jmeter_perf 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +19 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +18 -0
- data/DSL.md +235 -0
- data/README.md +24 -0
- data/Rakefile +12 -0
- data/example/Gemfile +39 -0
- data/example/Gemfile.lock +232 -0
- data/example/README.md +3 -0
- data/example/Rakefile +6 -0
- data/example/app/controllers/application_controller.rb +2 -0
- data/example/app/controllers/test_controller.rb +15 -0
- data/example/bin/bundle +109 -0
- data/example/bin/docker-entrypoint +8 -0
- data/example/bin/rails +4 -0
- data/example/bin/rake +4 -0
- data/example/bin/setup +33 -0
- data/example/config/application.rb +44 -0
- data/example/config/boot.rb +3 -0
- data/example/config/credentials.yml.enc +1 -0
- data/example/config/database.yml +25 -0
- data/example/config/environment.rb +5 -0
- data/example/config/environments/development.rb +64 -0
- data/example/config/environments/production.rb +82 -0
- data/example/config/environments/test.rb +61 -0
- data/example/config/initializers/cors.rb +16 -0
- data/example/config/initializers/filter_parameter_logging.rb +8 -0
- data/example/config/initializers/inflections.rb +16 -0
- data/example/config/locales/en.yml +31 -0
- data/example/config/puma.rb +35 -0
- data/example/config/routes.rb +5 -0
- data/example/config.ru +7 -0
- data/example/fast.log +49 -0
- data/example/jmeter.log +28 -0
- data/example/lib/tasks/test.rake +40 -0
- data/example/log/.keep +0 -0
- data/example/public/robots.txt +1 -0
- data/example/random.log +49 -0
- data/example/slow.log +49 -0
- data/example/vendor/.keep +0 -0
- data/lib/Rakefile +4 -0
- data/lib/jmeter_perf/dsl/access_log_sampler.rb +38 -0
- data/lib/jmeter_perf/dsl/aggregate_graph.rb +61 -0
- data/lib/jmeter_perf/dsl/aggregate_report.rb +61 -0
- data/lib/jmeter_perf/dsl/ajp13_sampler.rb +47 -0
- data/lib/jmeter_perf/dsl/assertion_results.rb +61 -0
- data/lib/jmeter_perf/dsl/bean_shell_assertion.rb +34 -0
- data/lib/jmeter_perf/dsl/bean_shell_listener.rb +34 -0
- data/lib/jmeter_perf/dsl/bean_shell_postprocessor.rb +34 -0
- data/lib/jmeter_perf/dsl/bean_shell_preprocessor.rb +34 -0
- data/lib/jmeter_perf/dsl/bean_shell_sampler.rb +34 -0
- data/lib/jmeter_perf/dsl/bean_shell_timer.rb +34 -0
- data/lib/jmeter_perf/dsl/bsf_assertion.rb +34 -0
- data/lib/jmeter_perf/dsl/bsf_listener.rb +34 -0
- data/lib/jmeter_perf/dsl/bsf_postprocessor.rb +34 -0
- data/lib/jmeter_perf/dsl/bsf_preprocessor.rb +34 -0
- data/lib/jmeter_perf/dsl/bsf_sampler.rb +34 -0
- data/lib/jmeter_perf/dsl/bsf_timer.rb +34 -0
- data/lib/jmeter_perf/dsl/compare_assertion.rb +33 -0
- data/lib/jmeter_perf/dsl/comparison_assertion_visualizer.rb +61 -0
- data/lib/jmeter_perf/dsl/constant_throughput_timer.rb +32 -0
- data/lib/jmeter_perf/dsl/constant_timer.rb +31 -0
- data/lib/jmeter_perf/dsl/counter.rb +37 -0
- data/lib/jmeter_perf/dsl/css_jquery_extractor.rb +37 -0
- data/lib/jmeter_perf/dsl/csv_data_set_config.rb +39 -0
- data/lib/jmeter_perf/dsl/debug_postprocessor.rb +34 -0
- data/lib/jmeter_perf/dsl/debug_sampler.rb +33 -0
- data/lib/jmeter_perf/dsl/distribution_graphalpha.rb +61 -0
- data/lib/jmeter_perf/dsl/duration_assertion.rb +31 -0
- data/lib/jmeter_perf/dsl/for_each_controller.rb +33 -0
- data/lib/jmeter_perf/dsl/ftp_request.rb +40 -0
- data/lib/jmeter_perf/dsl/ftp_request_defaults.rb +38 -0
- data/lib/jmeter_perf/dsl/gaussian_random_timer.rb +32 -0
- data/lib/jmeter_perf/dsl/generate_summary_results.rb +29 -0
- data/lib/jmeter_perf/dsl/graph_results.rb +61 -0
- data/lib/jmeter_perf/dsl/html_assertion.rb +36 -0
- data/lib/jmeter_perf/dsl/html_link_parser.rb +29 -0
- data/lib/jmeter_perf/dsl/html_parameter_mask.rb +38 -0
- data/lib/jmeter_perf/dsl/http_authorization_manager.rb +39 -0
- data/lib/jmeter_perf/dsl/http_cache_manager.rb +32 -0
- data/lib/jmeter_perf/dsl/http_cookie_manager.rb +34 -0
- data/lib/jmeter_perf/dsl/http_header_manager.rb +36 -0
- data/lib/jmeter_perf/dsl/http_request.rb +47 -0
- data/lib/jmeter_perf/dsl/http_request_defaults.rb +53 -0
- data/lib/jmeter_perf/dsl/http_url_rewriting_modifier.rb +36 -0
- data/lib/jmeter_perf/dsl/if_controller.rb +33 -0
- data/lib/jmeter_perf/dsl/include_controller.rb +31 -0
- data/lib/jmeter_perf/dsl/j_unit_request.rb +43 -0
- data/lib/jmeter_perf/dsl/java_request.rb +75 -0
- data/lib/jmeter_perf/dsl/java_request_defaults.rb +75 -0
- data/lib/jmeter_perf/dsl/jdbc_connection_configuration.rb +43 -0
- data/lib/jmeter_perf/dsl/jdbc_postprocessor.rb +39 -0
- data/lib/jmeter_perf/dsl/jdbc_preprocessor.rb +39 -0
- data/lib/jmeter_perf/dsl/jdbc_request.rb +39 -0
- data/lib/jmeter_perf/dsl/jms_pointto_point.rb +47 -0
- data/lib/jmeter_perf/dsl/jms_publisher.rb +49 -0
- data/lib/jmeter_perf/dsl/jms_subscriber.rb +41 -0
- data/lib/jmeter_perf/dsl/json_path_postprocessor.rb +33 -0
- data/lib/jmeter_perf/dsl/jsr223_assertion.rb +35 -0
- data/lib/jmeter_perf/dsl/jsr223_listener.rb +35 -0
- data/lib/jmeter_perf/dsl/jsr223_postprocessor.rb +35 -0
- data/lib/jmeter_perf/dsl/jsr223_preprocessor.rb +35 -0
- data/lib/jmeter_perf/dsl/jsr223_sampler.rb +35 -0
- data/lib/jmeter_perf/dsl/jsr223_timer.rb +35 -0
- data/lib/jmeter_perf/dsl/keystore_configuration.rb +34 -0
- data/lib/jmeter_perf/dsl/ldap_extended_request.rb +48 -0
- data/lib/jmeter_perf/dsl/ldap_extended_request_defaults.rb +48 -0
- data/lib/jmeter_perf/dsl/ldap_request.rb +41 -0
- data/lib/jmeter_perf/dsl/ldap_request_defaults.rb +45 -0
- data/lib/jmeter_perf/dsl/login_config_element.rb +32 -0
- data/lib/jmeter_perf/dsl/loop_controller.rb +32 -0
- data/lib/jmeter_perf/dsl/mail_reader_sampler.rb +43 -0
- data/lib/jmeter_perf/dsl/mailer_visualizer.rb +70 -0
- data/lib/jmeter_perf/dsl/md5_hex_assertion.rb +31 -0
- data/lib/jmeter_perf/dsl/module_controller.rb +31 -0
- data/lib/jmeter_perf/dsl/monitor_results.rb +61 -0
- data/lib/jmeter_perf/dsl/once_only_controller.rb +29 -0
- data/lib/jmeter_perf/dsl/os_process_sampler.rb +40 -0
- data/lib/jmeter_perf/dsl/poisson_random_timer.rb +32 -0
- data/lib/jmeter_perf/dsl/random_controller.rb +31 -0
- data/lib/jmeter_perf/dsl/random_order_controller.rb +29 -0
- data/lib/jmeter_perf/dsl/random_variable.rb +36 -0
- data/lib/jmeter_perf/dsl/recording_controller.rb +29 -0
- data/lib/jmeter_perf/dsl/reg_ex_user_parameters.rb +33 -0
- data/lib/jmeter_perf/dsl/regular_expression_extractor.rb +38 -0
- data/lib/jmeter_perf/dsl/response_assertion.rb +37 -0
- data/lib/jmeter_perf/dsl/response_time_graph.rb +61 -0
- data/lib/jmeter_perf/dsl/result_status_action_handler.rb +31 -0
- data/lib/jmeter_perf/dsl/runtime_controller.rb +31 -0
- data/lib/jmeter_perf/dsl/save_responses_to_a_file.rb +35 -0
- data/lib/jmeter_perf/dsl/simple_config_element.rb +29 -0
- data/lib/jmeter_perf/dsl/simple_controller.rb +29 -0
- data/lib/jmeter_perf/dsl/simple_data_writer.rb +61 -0
- data/lib/jmeter_perf/dsl/smime_assertion.rb +41 -0
- data/lib/jmeter_perf/dsl/smtp_sampler.rb +57 -0
- data/lib/jmeter_perf/dsl/soap_xml_rpc_request.rb +39 -0
- data/lib/jmeter_perf/dsl/spline_visualizer.rb +61 -0
- data/lib/jmeter_perf/dsl/summary_report.rb +61 -0
- data/lib/jmeter_perf/dsl/switch_controller.rb +31 -0
- data/lib/jmeter_perf/dsl/synchronizing_timer.rb +32 -0
- data/lib/jmeter_perf/dsl/tcp_sampler.rb +39 -0
- data/lib/jmeter_perf/dsl/tcp_sampler_config.rb +37 -0
- data/lib/jmeter_perf/dsl/test_action.rb +33 -0
- data/lib/jmeter_perf/dsl/test_fragment.rb +29 -0
- data/lib/jmeter_perf/dsl/test_plan.rb +37 -0
- data/lib/jmeter_perf/dsl/thread_group.rb +43 -0
- data/lib/jmeter_perf/dsl/throughput_controller.rb +38 -0
- data/lib/jmeter_perf/dsl/transaction_controller.rb +32 -0
- data/lib/jmeter_perf/dsl/uniform_random_timer.rb +32 -0
- data/lib/jmeter_perf/dsl/user_defined_variables.rb +39 -0
- data/lib/jmeter_perf/dsl/user_parameters.rb +36 -0
- data/lib/jmeter_perf/dsl/view_results_in_table.rb +61 -0
- data/lib/jmeter_perf/dsl/view_results_tree.rb +61 -0
- data/lib/jmeter_perf/dsl/while_controller.rb +31 -0
- data/lib/jmeter_perf/dsl/x_path_assertion.rb +37 -0
- data/lib/jmeter_perf/dsl/x_path_extractor.rb +37 -0
- data/lib/jmeter_perf/dsl/xml_assertion.rb +29 -0
- data/lib/jmeter_perf/dsl/xml_schema_assertion.rb +31 -0
- data/lib/jmeter_perf/extend/assertions/response_assertion.rb +38 -0
- data/lib/jmeter_perf/extend/config_elements/header_manager.rb +13 -0
- data/lib/jmeter_perf/extend/config_elements/http_cache_manager.rb +12 -0
- data/lib/jmeter_perf/extend/config_elements/http_cookie_manager.rb +39 -0
- data/lib/jmeter_perf/extend/config_elements/http_request_defaults.rb +55 -0
- data/lib/jmeter_perf/extend/config_elements/user_defined_variables.rb +13 -0
- data/lib/jmeter_perf/extend/config_elements/user_parameters.rb +31 -0
- data/lib/jmeter_perf/extend/controllers/foreach_controller.rb +31 -0
- data/lib/jmeter_perf/extend/controllers/loop_controller.rb +11 -0
- data/lib/jmeter_perf/extend/controllers/module_controller.rb +26 -0
- data/lib/jmeter_perf/extend/controllers/throughput_controller.rb +15 -0
- data/lib/jmeter_perf/extend/controllers/transaction_controller.rb +14 -0
- data/lib/jmeter_perf/extend/misc/exists.rb +13 -0
- data/lib/jmeter_perf/extend/misc/rsync.rb +24 -0
- data/lib/jmeter_perf/extend/misc/uuid.rb +12 -0
- data/lib/jmeter_perf/extend/misc/with_helpers.rb +27 -0
- data/lib/jmeter_perf/extend/plugins/jmeter_plugins.rb +124 -0
- data/lib/jmeter_perf/extend/processors/extract.rb +27 -0
- data/lib/jmeter_perf/extend/processors/regular_expression_extractor.rb +27 -0
- data/lib/jmeter_perf/extend/samplers/http_request.rb +66 -0
- data/lib/jmeter_perf/extend/samplers/jms_pointtopoint.rb +23 -0
- data/lib/jmeter_perf/extend/samplers/soapxmlrpc_request.rb +10 -0
- data/lib/jmeter_perf/extend/threads/thread_group.rb +19 -0
- data/lib/jmeter_perf/extend/timers/constant_throughput_timer.rb +11 -0
- data/lib/jmeter_perf/extend/timers/random_timer.rb +14 -0
- data/lib/jmeter_perf/helpers/dsl_generator.rb +157 -0
- data/lib/jmeter_perf/helpers/fallback_content_proxy.rb +96 -0
- data/lib/jmeter_perf/helpers/helper.rb +63 -0
- data/lib/jmeter_perf/helpers/parser.rb +143 -0
- data/lib/jmeter_perf/helpers/running_statistics.rb +62 -0
- data/lib/jmeter_perf/helpers/string.rb +60 -0
- data/lib/jmeter_perf/helpers/user-agents.rb +42 -0
- data/lib/jmeter_perf/plugins/active_threads_over_time.rb +59 -0
- data/lib/jmeter_perf/plugins/composite_graph.rb +77 -0
- data/lib/jmeter_perf/plugins/console_status_logger.rb +19 -0
- data/lib/jmeter_perf/plugins/dummy_sampler.rb +30 -0
- data/lib/jmeter_perf/plugins/jmx_collector.rb +74 -0
- data/lib/jmeter_perf/plugins/json_path_assertion.rb +23 -0
- data/lib/jmeter_perf/plugins/json_path_extractor.rb +22 -0
- data/lib/jmeter_perf/plugins/latencies_over_time.rb +53 -0
- data/lib/jmeter_perf/plugins/loadosophia_uploader.rb +66 -0
- data/lib/jmeter_perf/plugins/perfmon_collector.rb +87 -0
- data/lib/jmeter_perf/plugins/redis_data_set.rb +43 -0
- data/lib/jmeter_perf/plugins/response_codes_per_second.rb +53 -0
- data/lib/jmeter_perf/plugins/response_times_distribution.rb +53 -0
- data/lib/jmeter_perf/plugins/response_times_over_time.rb +53 -0
- data/lib/jmeter_perf/plugins/response_times_percentiles.rb +54 -0
- data/lib/jmeter_perf/plugins/stepping_thread_group.rb +34 -0
- data/lib/jmeter_perf/plugins/transactions_per_second.rb +53 -0
- data/lib/jmeter_perf/plugins/ultimate_thread_group.rb +28 -0
- data/lib/jmeter_perf/plugins/variable_throughput_timer.rb +35 -0
- data/lib/jmeter_perf/report/comparator.rb +258 -0
- data/lib/jmeter_perf/report/summary.rb +268 -0
- data/lib/jmeter_perf/tasks/dsl.rake +19 -0
- data/lib/jmeter_perf/version.rb +5 -0
- data/lib/jmeter_perf/views/report_template.html.erb +114 -0
- data/lib/jmeter_perf.rb +183 -0
- data/lib/specifications/idl.xml +1494 -0
- data/sig/jmeter_perf.rbs +195 -0
- data/sorbet/config +5 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +585 -0
- data/sorbet/rbi/gems/bump@0.10.0.rbi +169 -0
- data/sorbet/rbi/gems/byebug@11.1.3.rbi +3607 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +3427 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.1.rbi +1131 -0
- data/sorbet/rbi/gems/docile@1.4.1.rbi +377 -0
- data/sorbet/rbi/gems/erubi@1.13.0.rbi +150 -0
- data/sorbet/rbi/gems/json@2.7.2.rbi +1562 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14238 -0
- data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +240 -0
- data/sorbet/rbi/gems/method_source@1.1.0.rbi +304 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
- data/sorbet/rbi/gems/nokogiri@1.16.7.rbi +7311 -0
- data/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
- data/sorbet/rbi/gems/parser@3.3.5.0.rbi +5519 -0
- data/sorbet/rbi/gems/prism@1.2.0.rbi +39085 -0
- data/sorbet/rbi/gems/pry-byebug@3.10.1.rbi +1151 -0
- data/sorbet/rbi/gems/pry@0.14.2.rbi +10076 -0
- data/sorbet/rbi/gems/racc@1.8.1.rbi +162 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +403 -0
- data/sorbet/rbi/gems/rake@13.2.1.rbi +3074 -0
- data/sorbet/rbi/gems/rbi@0.2.1.rbi +4535 -0
- data/sorbet/rbi/gems/rbtree3@0.7.1.rbi +9 -0
- data/sorbet/rbi/gems/regexp_parser@2.9.2.rbi +3772 -0
- data/sorbet/rbi/gems/rexml@3.3.8.rbi +4858 -0
- data/sorbet/rbi/gems/rspec-core@3.13.1.rbi +11132 -0
- data/sorbet/rbi/gems/rspec-expectations@3.13.3.rbi +8183 -0
- data/sorbet/rbi/gems/rspec-mocks@3.13.1.rbi +5341 -0
- data/sorbet/rbi/gems/rspec-support@3.13.1.rbi +1630 -0
- data/sorbet/rbi/gems/rspec@3.13.0.rbi +83 -0
- data/sorbet/rbi/gems/rubocop-ast@1.32.3.rbi +7054 -0
- data/sorbet/rbi/gems/rubocop-performance@1.21.1.rbi +9 -0
- data/sorbet/rbi/gems/rubocop@1.65.1.rbi +58182 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1318 -0
- data/sorbet/rbi/gems/simplecov-html@0.13.1.rbi +225 -0
- data/sorbet/rbi/gems/simplecov@0.22.0.rbi +2149 -0
- data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +9 -0
- data/sorbet/rbi/gems/spoom@1.5.0.rbi +4932 -0
- data/sorbet/rbi/gems/standard-custom@1.0.2.rbi +9 -0
- data/sorbet/rbi/gems/standard-performance@1.4.0.rbi +9 -0
- data/sorbet/rbi/gems/standard@1.40.0.rbi +926 -0
- data/sorbet/rbi/gems/tapioca@0.16.3.rbi +3596 -0
- data/sorbet/rbi/gems/tdigest@0.2.1.rbi +170 -0
- data/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
- data/sorbet/rbi/gems/unicode-display_width@2.6.0.rbi +66 -0
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
- data/sorbet/rbi/gems/yard@0.9.37.rbi +18379 -0
- data/sorbet/rbi/todo.rbi +31 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +15 -0
- data/tasks/dsl.rake +22 -0
- metadata +355 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
require "csv"
|
|
2
|
+
require "timeout"
|
|
3
|
+
require_relative "../helpers/running_statistics"
|
|
4
|
+
module JmeterPerf
|
|
5
|
+
module Report
|
|
6
|
+
# Summary provides a statistical analysis of performance test results by processing
|
|
7
|
+
# JMeter JTL files. It calculates metrics such as average response time, error percentage,
|
|
8
|
+
# min/max response times, and percentiles, helping to understand the distribution of
|
|
9
|
+
# response times across requests.
|
|
10
|
+
# @note This class uses a TDigest data structure to keep statistics "close enough". Accuracy is not guaranteed.
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
# @!attribute [rw] avg
|
|
14
|
+
# @return [Float] the average response time
|
|
15
|
+
# @!attribute [rw] error_percentage
|
|
16
|
+
# @return [Float] the error percentage across all requests
|
|
17
|
+
# @!attribute [rw] max
|
|
18
|
+
# @return [Integer] the maximum response time encountered
|
|
19
|
+
# @!attribute [rw] min
|
|
20
|
+
# @return [Integer] the minimum response time encountered
|
|
21
|
+
# @!attribute [rw] p10
|
|
22
|
+
# @return [Float] the 10th percentile of response times
|
|
23
|
+
# @!attribute [rw] p50
|
|
24
|
+
# @return [Float] the median (50th percentile) of response times
|
|
25
|
+
# @!attribute [rw] p95
|
|
26
|
+
# @return [Float] the 95th percentile of response times
|
|
27
|
+
# @!attribute [rw] requests_per_minute
|
|
28
|
+
# @return [Float] the requests per minute rate
|
|
29
|
+
# @!attribute [rw] response_codes
|
|
30
|
+
# @return [Hash<String, Integer>] a hash of response codes with their respective counts
|
|
31
|
+
# @!attribute [rw] standard_deviation
|
|
32
|
+
# @return [Float] the standard deviation of response times
|
|
33
|
+
# @!attribute [rw] total_bytes
|
|
34
|
+
# @return [Integer] the total number of bytes received
|
|
35
|
+
# @!attribute [rw] total_elapsed_time
|
|
36
|
+
# @return [Integer] the total elapsed time in milliseconds
|
|
37
|
+
# @!attribute [rw] total_errors
|
|
38
|
+
# @return [Integer] the total number of errors encountered
|
|
39
|
+
# @!attribute [rw] total_latency
|
|
40
|
+
# @return [Integer] the total latency time across all requests
|
|
41
|
+
# @!attribute [rw] total_requests
|
|
42
|
+
# @return [Integer] the total number of requests processed
|
|
43
|
+
# @!attribute [rw] total_sent_bytes
|
|
44
|
+
# @return [Integer] the total number of bytes sent
|
|
45
|
+
# @!attribute [rw] csv_error_lines
|
|
46
|
+
# @return [Array<Integer>] the line numbers of where CSV errors were encountered
|
|
47
|
+
# @!attribute [rw] total_run_time
|
|
48
|
+
# @return [Integer] the total run time in seconds
|
|
49
|
+
# @!attribute [rw] name
|
|
50
|
+
# @return [String] the name of the summary, derived from the file path if not provided
|
|
51
|
+
# @!attribute [rw] response_codes
|
|
52
|
+
# @return [Hash<String, Integer>] a hash of response codes with their respective counts
|
|
53
|
+
# @!attribute [rw] csv_error_lines
|
|
54
|
+
# @return [Array<Integer>] the line numbers of where CSV errors were encountered
|
|
55
|
+
class Summary
|
|
56
|
+
# JTL file headers used for parsing CSV rows
|
|
57
|
+
JTL_HEADER = %i[
|
|
58
|
+
timeStamp
|
|
59
|
+
elapsed
|
|
60
|
+
label
|
|
61
|
+
responseCode
|
|
62
|
+
responseMessage
|
|
63
|
+
threadName
|
|
64
|
+
dataType
|
|
65
|
+
success
|
|
66
|
+
failureMessage
|
|
67
|
+
bytes
|
|
68
|
+
sentBytes
|
|
69
|
+
grpThreads
|
|
70
|
+
allThreads
|
|
71
|
+
URL
|
|
72
|
+
Latency
|
|
73
|
+
IdleTime
|
|
74
|
+
Connect
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# @return [Hash<String, Symbol>] a mapping of CSV headers to their corresponding attribute symbols.
|
|
78
|
+
CSV_HEADER_MAPPINGS = {
|
|
79
|
+
"Name" => :name,
|
|
80
|
+
"Average Response Time" => :avg,
|
|
81
|
+
"Error Percentage" => :error_percentage,
|
|
82
|
+
"Max Response Time" => :max,
|
|
83
|
+
"Min Response Time" => :min,
|
|
84
|
+
"10th Percentile" => :p10,
|
|
85
|
+
"Median (50th Percentile)" => :p50,
|
|
86
|
+
"95th Percentile" => :p95,
|
|
87
|
+
"Requests Per Minute" => :requests_per_minute,
|
|
88
|
+
"Standard Deviation" => :standard_deviation,
|
|
89
|
+
"Total Run Time" => :total_run_time,
|
|
90
|
+
"Total Bytes" => :total_bytes,
|
|
91
|
+
"Total Elapsed Time" => :total_elapsed_time,
|
|
92
|
+
"Total Errors" => :total_errors,
|
|
93
|
+
"Total Latency" => :total_latency,
|
|
94
|
+
"Total Requests" => :total_requests,
|
|
95
|
+
"Total Sent Bytes" => :total_sent_bytes
|
|
96
|
+
# "Response Code 200" => :response_codes["200"],
|
|
97
|
+
# "Response Code 500" => :response_codes["500"] etc.
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
attr_accessor(*CSV_HEADER_MAPPINGS.values)
|
|
101
|
+
# Response codes have multiple keys, so we need to handle them separately
|
|
102
|
+
attr_accessor :response_codes
|
|
103
|
+
# CSV Error Lines are an array of integers that get delimited by ":" when written to the CSV
|
|
104
|
+
attr_accessor :csv_error_lines
|
|
105
|
+
alias_method :rpm, :requests_per_minute
|
|
106
|
+
alias_method :std, :standard_deviation
|
|
107
|
+
alias_method :median, :p50
|
|
108
|
+
|
|
109
|
+
# Reads a generated CSV report and sets all appropriate attributes.
|
|
110
|
+
#
|
|
111
|
+
# @param csv_path [String] the file path of the CSV report to read
|
|
112
|
+
# @return [Summary] a new Summary instance with the parsed data
|
|
113
|
+
def self.read(csv_path)
|
|
114
|
+
summary = new(file_path: csv_path)
|
|
115
|
+
CSV.foreach(csv_path, headers: true) do |row|
|
|
116
|
+
metric = row["Metric"]
|
|
117
|
+
value = row["Value"]
|
|
118
|
+
|
|
119
|
+
if metric == "Name"
|
|
120
|
+
summary.name = value
|
|
121
|
+
elsif metric.start_with?("Response Code")
|
|
122
|
+
code = metric.split.last
|
|
123
|
+
summary.response_codes[code] = value.to_i
|
|
124
|
+
elsif metric == "CSV Errors"
|
|
125
|
+
summary.csv_error_lines = value.split(":").map(&:to_i)
|
|
126
|
+
elsif CSV_HEADER_MAPPINGS.key?(metric)
|
|
127
|
+
summary.public_send(:"#{CSV_HEADER_MAPPINGS[metric]}=", value.include?(".") ? value.to_f : value.to_i)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
summary
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Initializes a new Summary instance for analyzing performance data.
|
|
134
|
+
#
|
|
135
|
+
# @param file_path [String] the file path of the performance file to summarize. Either a JTL or CSV file.
|
|
136
|
+
# @param name [String, nil] an optional name for the summary, derived from the file path if not provided (default: nil)
|
|
137
|
+
# @param jtl_read_timeout [Integer] the maximum number of seconds to wait for a line read (default: 3)
|
|
138
|
+
def initialize(file_path:, name: nil, jtl_read_timeout: 3)
|
|
139
|
+
@name = name || file_path.to_s.tr("/", "_")
|
|
140
|
+
@jtl_read_timeout = jtl_read_timeout
|
|
141
|
+
@finished = false
|
|
142
|
+
@running_statistics_helper = JmeterPerf::Helpers::RunningStatistisc.new
|
|
143
|
+
|
|
144
|
+
@max = 0
|
|
145
|
+
@min = 1_000_000
|
|
146
|
+
@response_codes = Hash.new { |h, k| h[k.to_s] = 0 }
|
|
147
|
+
@total_bytes = 0
|
|
148
|
+
@total_elapsed_time = 0
|
|
149
|
+
@total_errors = 0
|
|
150
|
+
@total_latency = 0
|
|
151
|
+
@total_requests = 0
|
|
152
|
+
@total_sent_bytes = 0
|
|
153
|
+
@csv_error_lines = []
|
|
154
|
+
|
|
155
|
+
@file_path = file_path
|
|
156
|
+
|
|
157
|
+
@start_time = nil
|
|
158
|
+
@end_time = nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Marks the summary as finished, allowing any pending asynchronous operations to complete.
|
|
162
|
+
#
|
|
163
|
+
# @return [void]
|
|
164
|
+
def finish!
|
|
165
|
+
@finished = true
|
|
166
|
+
@processing_jtl_thread.join if @processing_jtl_thread&.alive?
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Generates a CSV report with the given output file.
|
|
170
|
+
#
|
|
171
|
+
# The CSV report includes the following:
|
|
172
|
+
# - A header row with "Metric" and "Value".
|
|
173
|
+
# - Rows for each metric and its corresponding value from `CSV_HEADER_MAPPINGS`.
|
|
174
|
+
# - Rows for each response code and its count from `@response_codes`.
|
|
175
|
+
# - A row for CSV errors, concatenated with ":".
|
|
176
|
+
#
|
|
177
|
+
# @param output_file [String] The path to the output CSV file.
|
|
178
|
+
# @return [void]
|
|
179
|
+
def write_csv(output_file)
|
|
180
|
+
CSV.open(output_file, "wb") do |csv|
|
|
181
|
+
csv << ["Metric", "Value"]
|
|
182
|
+
CSV_HEADER_MAPPINGS.each do |metric, value|
|
|
183
|
+
csv << [metric, public_send(value)]
|
|
184
|
+
end
|
|
185
|
+
@response_codes.each do |code, count|
|
|
186
|
+
csv << ["Response Code #{code}", count]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
csv << ["CSV Errors", @csv_error_lines.join(":")]
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Starts streaming and processing JTL file content asynchronously.
|
|
194
|
+
#
|
|
195
|
+
# @return [Thread] a thread that handles the asynchronous file streaming and parsing
|
|
196
|
+
def stream_jtl_async
|
|
197
|
+
@processing_jtl_thread = Thread.new do
|
|
198
|
+
sleep 0.1 until File.exist?(@file_path) # Wait for the file to be created
|
|
199
|
+
|
|
200
|
+
File.open(@file_path, "r") do |file|
|
|
201
|
+
file.seek(0, IO::SEEK_END)
|
|
202
|
+
until @finished && file.eof?
|
|
203
|
+
line = file.gets
|
|
204
|
+
next unless line # Skip if no line was read
|
|
205
|
+
|
|
206
|
+
# Process only if the line is complete (ends with a newline)
|
|
207
|
+
read_until_complete_line(file, line)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Summarizes the collected data by calculating statistical metrics and error rates.
|
|
214
|
+
#
|
|
215
|
+
# @return [void]
|
|
216
|
+
def summarize_data!
|
|
217
|
+
@p10, @p50, @p95 = @running_statistics_helper.get_percentiles(0.1, 0.5, 0.95)
|
|
218
|
+
@error_percentage = (@total_errors.to_f / @total_requests) * 100
|
|
219
|
+
@avg = @running_statistics_helper.avg
|
|
220
|
+
@total_run_time = ((@end_time - @start_time) / 1000).to_f # Convert milliseconds to seconds
|
|
221
|
+
@requests_per_minute = @total_run_time.zero? ? 0 : (@total_requests / @total_run_time) * 60.0
|
|
222
|
+
@standard_deviation = @running_statistics_helper.std
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private
|
|
226
|
+
|
|
227
|
+
def read_until_complete_line(file, line)
|
|
228
|
+
return if file.lineno == 1 # Skip the header row
|
|
229
|
+
Timeout.timeout(@jtl_read_timeout) do
|
|
230
|
+
until line.end_with?("\n")
|
|
231
|
+
sleep 0.1
|
|
232
|
+
line += file.gets.to_s
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
parse_csv_row(line)
|
|
236
|
+
rescue Timeout::Error
|
|
237
|
+
puts "Timeout waiting for line to complete: #{line}"
|
|
238
|
+
raise
|
|
239
|
+
rescue CSV::MalformedCSVError
|
|
240
|
+
@csv_error_lines << file.lineno
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def parse_csv_row(line)
|
|
244
|
+
CSV.parse(line, headers: JTL_HEADER, liberal_parsing: true).each do |row|
|
|
245
|
+
line_item = row.to_hash
|
|
246
|
+
elapsed = line_item.fetch(:elapsed).to_i
|
|
247
|
+
timestamp = line_item.fetch(:timeStamp).to_i
|
|
248
|
+
|
|
249
|
+
# Update start and end times
|
|
250
|
+
@start_time = timestamp if @start_time.nil? || timestamp < @start_time
|
|
251
|
+
@end_time = timestamp + elapsed if @end_time.nil? || (timestamp + elapsed) > @end_time
|
|
252
|
+
|
|
253
|
+
# Continue with processing the row as before...
|
|
254
|
+
@running_statistics_helper.add_number(elapsed)
|
|
255
|
+
@total_requests += 1
|
|
256
|
+
@total_elapsed_time += elapsed
|
|
257
|
+
@response_codes[line_item.fetch(:responseCode)] += 1
|
|
258
|
+
@total_errors += (line_item.fetch(:success) == "true") ? 0 : 1
|
|
259
|
+
@total_bytes += line_item.fetch(:bytes, 0).to_i
|
|
260
|
+
@total_sent_bytes += line_item.fetch(:sentBytes, 0).to_i
|
|
261
|
+
@total_latency += line_item.fetch(:Latency).to_i
|
|
262
|
+
@min = [@min, elapsed].min
|
|
263
|
+
@max = [@max, elapsed].max
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
namespace :jmeter_perf do
|
|
2
|
+
desc "Generate JmeterPerf::DSL methods"
|
|
3
|
+
task :dsl_generate, [:dsl_dir, :idl_xml_path] do |t, args|
|
|
4
|
+
require_relative "../helpers/dsl_generator"
|
|
5
|
+
puts "Generating DSL methods..."
|
|
6
|
+
|
|
7
|
+
dsl_dir = Pathname(args[:dsl_dir]).expand_path
|
|
8
|
+
idl_xml_path = Pathname(args[:idl_xml_path]).expand_path
|
|
9
|
+
|
|
10
|
+
generator = JmeterPerf::Helpers::DSLGenerator.new(
|
|
11
|
+
dsl_dir: dsl_dir,
|
|
12
|
+
idl_xml_path: idl_xml_path
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
generator.generate
|
|
16
|
+
|
|
17
|
+
puts "Finished generating DSL methods."
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Comparison Report</title>
|
|
5
|
+
<style>
|
|
6
|
+
body {
|
|
7
|
+
font-family: Arial, sans-serif;
|
|
8
|
+
}
|
|
9
|
+
.summary-box {
|
|
10
|
+
margin-bottom: 20px;
|
|
11
|
+
padding: 10px;
|
|
12
|
+
border: 1px solid #ccc;
|
|
13
|
+
border-radius: 5px;
|
|
14
|
+
background-color: #f9f9f9;
|
|
15
|
+
}
|
|
16
|
+
table {
|
|
17
|
+
width: 100%;
|
|
18
|
+
border-collapse: collapse;
|
|
19
|
+
table-layout: fixed;
|
|
20
|
+
}
|
|
21
|
+
td {
|
|
22
|
+
border: 1px solid black;
|
|
23
|
+
padding: 8px;
|
|
24
|
+
text-align: center;
|
|
25
|
+
word-wrap: break-word;
|
|
26
|
+
}
|
|
27
|
+
.header-row td {
|
|
28
|
+
background-color: #f2f2f2;
|
|
29
|
+
font-weight: bold;
|
|
30
|
+
}
|
|
31
|
+
.bad {
|
|
32
|
+
background-color: #ffcccc;
|
|
33
|
+
}
|
|
34
|
+
.tooltip {
|
|
35
|
+
position: relative;
|
|
36
|
+
}
|
|
37
|
+
.tooltip .tooltiptext {
|
|
38
|
+
visibility: hidden;
|
|
39
|
+
width: 150px;
|
|
40
|
+
background-color: black;
|
|
41
|
+
color: #fff;
|
|
42
|
+
text-align: center;
|
|
43
|
+
border-radius: 6px;
|
|
44
|
+
padding: 5px;
|
|
45
|
+
position: absolute;
|
|
46
|
+
z-index: 1;
|
|
47
|
+
bottom: 100%;
|
|
48
|
+
left: 50%;
|
|
49
|
+
margin-left: -75px;
|
|
50
|
+
opacity: 0;
|
|
51
|
+
transition: opacity 0.3s;
|
|
52
|
+
}
|
|
53
|
+
.tooltip:hover .tooltiptext {
|
|
54
|
+
visibility: visible;
|
|
55
|
+
opacity: 1;
|
|
56
|
+
}
|
|
57
|
+
</style>
|
|
58
|
+
</head>
|
|
59
|
+
<body>
|
|
60
|
+
<% if @comparator.name %>
|
|
61
|
+
<h2>Comparison Report for <span style="text-decoration: underline;"><%= @comparator.name %></span></h2>
|
|
62
|
+
<% end %>
|
|
63
|
+
<div class="summary-box" id="summary-box">
|
|
64
|
+
<b>Report Generated:</b> <%= Time.now.strftime("%Y-%m-%d %H:%M:%S") %><br>
|
|
65
|
+
<b>Cohen's D:</b> <%= @comparator.cohens_d %><br>
|
|
66
|
+
<b>T Statistics:</b> <%= @comparator.t_statistic %><br>
|
|
67
|
+
<b>Summary:</b> <%= @comparator.human_rating %> in performance
|
|
68
|
+
</div>
|
|
69
|
+
<table>
|
|
70
|
+
<tr class="header-row">
|
|
71
|
+
<% ['Label', 'Total Requests', 'Total Elapsed Time', 'RPM', 'Errors', 'Error %', 'Min', 'Max', 'Avg', 'SD', 'P10', 'P50', 'P95'].each do |header| %>
|
|
72
|
+
<td class="tooltip"><%= header %>
|
|
73
|
+
<span class="tooltiptext"><%= "Description for #{header}" %></span>
|
|
74
|
+
</td>
|
|
75
|
+
<% end %>
|
|
76
|
+
</tr>
|
|
77
|
+
<% @reports.each_with_index do |report, index| %>
|
|
78
|
+
<tr>
|
|
79
|
+
<td><%= index == 0 ? "Base Metric" : "Test Metric" %></td>
|
|
80
|
+
<td><%= report.total_requests %></td>
|
|
81
|
+
<td><%= report.total_elapsed_time %></td>
|
|
82
|
+
<td><%= sprintf('%.2f', report.rpm) %></td>
|
|
83
|
+
<td class="<%= 'bad' if report.total_errors > 0 %>"><%= report.total_errors %></td>
|
|
84
|
+
<td class="<%= 'bad' if report.error_percentage > 0.0 %>"><%= sprintf('%.2f', report.error_percentage) %></td>
|
|
85
|
+
<td><%= report.min %></td>
|
|
86
|
+
<td><%= report.max %></td>
|
|
87
|
+
<td><%= sprintf('%.2f', report.avg) %></td>
|
|
88
|
+
<td><%= sprintf('%.2f', report.std) %></td>
|
|
89
|
+
<td><%= report.p10 %></td>
|
|
90
|
+
<td><%= report.p50 %></td>
|
|
91
|
+
<td><%= report.p95 %></td>
|
|
92
|
+
</tr>
|
|
93
|
+
<% end %>
|
|
94
|
+
</table>
|
|
95
|
+
<script type="module">
|
|
96
|
+
import ColorScale from "https://cdn.skypack.dev/color-scales";
|
|
97
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
98
|
+
const summaryBox = document.getElementById('summary-box');
|
|
99
|
+
const cohenValue = <%= @comparator.cohens_d %>;
|
|
100
|
+
let color;
|
|
101
|
+
|
|
102
|
+
if (cohenValue === 0) {
|
|
103
|
+
color = 'lightgray'; // Neutral color for zero
|
|
104
|
+
} else {
|
|
105
|
+
const colorScale = new ColorScale(-3, 2, ['#FF0000', '#FFFF00', '#008000']);
|
|
106
|
+
color = colorScale.getColor(cohenValue).toRGBString()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
summaryBox.style.backgroundColor = color;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
</script>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
data/lib/jmeter_perf.rb
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "jmeter_perf/version"
|
|
4
|
+
lib = File.dirname(File.absolute_path(__FILE__))
|
|
5
|
+
|
|
6
|
+
Dir.glob(File.join(lib, "jmeter_perf/report/*.rb")).each do |file|
|
|
7
|
+
require_relative file
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
Dir.glob(File.join(lib, "jmeter_perf/helpers/*.rb")).each do |file|
|
|
11
|
+
require_relative file
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
Dir.glob(File.join(lib, "jmeter_perf/dsl/*.rb")).each do |file|
|
|
15
|
+
require_relative file
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Dir.glob(File.join(lib, "jmeter_perf/extend/**/*.rb")).each do |file|
|
|
19
|
+
require_relative file
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Dir.glob(File.join(lib, "jmeter_perf/plugins/*.rb")).each do |file|
|
|
23
|
+
require_relative file
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# JmeterPerf module for handling performance testing with JMeter.
|
|
27
|
+
# This module provides methods to define and execute JMeter test plans.
|
|
28
|
+
module JmeterPerf
|
|
29
|
+
# Evaluates the test plan with the given parameters and block.
|
|
30
|
+
# @see https://github.com/jlurena/jmeter_perf/wiki/1.-DSL-Documentation DSL Documentation
|
|
31
|
+
# @param params [Hash] Parameters for the test plan (default: `{}`).
|
|
32
|
+
# @yield The block to define the test plan steps.
|
|
33
|
+
# @return [JmeterPerf::ExtendedDSL] The DSL instance with the configured test plan.
|
|
34
|
+
def self.test(params = {}, &block)
|
|
35
|
+
dsl = JmeterPerf::ExtendedDSL.new(params)
|
|
36
|
+
|
|
37
|
+
block_context = eval("self", block.binding, __FILE__, __LINE__)
|
|
38
|
+
proxy_context = JmeterPerf::Helpers::FallbackContextProxy.new(dsl, block_context)
|
|
39
|
+
begin
|
|
40
|
+
block_context.instance_variables.each do |ivar|
|
|
41
|
+
proxy_context.instance_variable_set(ivar, block_context.instance_variable_get(ivar))
|
|
42
|
+
end
|
|
43
|
+
proxy_context.instance_eval(&block)
|
|
44
|
+
ensure
|
|
45
|
+
block_context.instance_variables.each do |ivar|
|
|
46
|
+
block_context.instance_variable_set(ivar, proxy_context.instance_variable_get(ivar))
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
dsl
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# DSL class for defining JMeter test plans.
|
|
53
|
+
# Provides methods to generate, save, and run JMeter tests.
|
|
54
|
+
class ExtendedDSL < DSL
|
|
55
|
+
include JmeterPerf::Helpers::Parser
|
|
56
|
+
attr_accessor :root
|
|
57
|
+
|
|
58
|
+
# Initializes an ExtendedDSL object with the provided parameters.
|
|
59
|
+
# @param params [Hash] Parameters for the test plan (default: `{}`).
|
|
60
|
+
def initialize(params = {})
|
|
61
|
+
@root = Nokogiri::XML(JmeterPerf::Helpers::String.strip_heredoc(
|
|
62
|
+
<<-EOF
|
|
63
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
64
|
+
<jmeterTestPlan version="1.2" properties="3.1" jmeter="3.1" ruby-jmeter="3.0">
|
|
65
|
+
<hashTree>
|
|
66
|
+
</hashTree>
|
|
67
|
+
</jmeterTestPlan>
|
|
68
|
+
EOF
|
|
69
|
+
))
|
|
70
|
+
node = JmeterPerf::DSL::TestPlan.new(params)
|
|
71
|
+
@current_node = @root.at_xpath("//jmeterTestPlan/hashTree")
|
|
72
|
+
@current_node = attach_to_last(node)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Saves the test plan as a JMX file.
|
|
76
|
+
#
|
|
77
|
+
# @param out_jmx [String] The path for the output JMX file (default: `"ruby-jmeter.jmx"`).
|
|
78
|
+
def jmx(out_jmx: "ruby-jmeter.jmx")
|
|
79
|
+
File.write(out_jmx, doc.to_xml(indent: 2))
|
|
80
|
+
logger.info "JMX saved to: #{out_jmx}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Runs the test plan with the specified configuration.
|
|
84
|
+
#
|
|
85
|
+
# @param name [String] The name of the test run (default: `"ruby-jmeter"`).
|
|
86
|
+
# @param jmeter_path [String] Path to the JMeter executable (default: `"jmeter"`).
|
|
87
|
+
# @param out_jmx [String] The filename for the output JMX file (default: `"ruby-jmeter.jmx"`).
|
|
88
|
+
# @param out_jtl [String] The filename for the output JTL file (default: `"jmeter.jtl"`).
|
|
89
|
+
# @param out_jmeter_log [String] The filename for the JMeter log file (default: `"jmeter.log"`).
|
|
90
|
+
# @param out_cmd_log [String] The filename for the command log file (default: `"jmeter-cmd.log"`).
|
|
91
|
+
# @param jtl_read_timeout [Integer] The maximum number of seconds to wait for a line read (default: `3`).
|
|
92
|
+
# @return [JmeterPerf::Report::Summary] The summary report of the test run.
|
|
93
|
+
# @raise [RuntimeError] If the test execution fails.
|
|
94
|
+
def run(
|
|
95
|
+
name: "ruby-jmeter",
|
|
96
|
+
jmeter_path: "jmeter",
|
|
97
|
+
out_jmx: "ruby-jmeter.jmx",
|
|
98
|
+
out_jtl: "jmeter.jtl",
|
|
99
|
+
out_jmeter_log: "jmeter.log",
|
|
100
|
+
out_cmd_log: "jmeter-cmd.log",
|
|
101
|
+
jtl_read_timeout: 3
|
|
102
|
+
)
|
|
103
|
+
jmx(out_jmx:)
|
|
104
|
+
logger.warn "Executing #{out_jmx} test plan locally ..."
|
|
105
|
+
|
|
106
|
+
cmd = <<~CMD.strip
|
|
107
|
+
#{jmeter_path} -n -t #{out_jmx} -j #{out_jmeter_log} -l #{out_jtl}
|
|
108
|
+
CMD
|
|
109
|
+
|
|
110
|
+
summary = JmeterPerf::Report::Summary.new(file_path: out_jtl, name:, jtl_read_timeout:)
|
|
111
|
+
jtl_process_thread = summary.stream_jtl_async
|
|
112
|
+
|
|
113
|
+
File.open(out_cmd_log, "w") do |f|
|
|
114
|
+
pid = Process.spawn(cmd, out: f, err: [:child, :out])
|
|
115
|
+
Process.waitpid(pid)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
summary.finish! # Notify jtl collection that JTL cmd finished
|
|
119
|
+
jtl_process_thread.join # Join main thread and wait for it to finish
|
|
120
|
+
|
|
121
|
+
unless $?.exitstatus.zero?
|
|
122
|
+
logger.error("Failed to run #{cmd}. See #{out_cmd_log} and #{out_jmeter_log} for details.")
|
|
123
|
+
raise "Exit status: #{$?.exitstatus}"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
summary.summarize_data!
|
|
127
|
+
logger.info "[Test Plan Execution Completed Successfully] JTL saved to: #{out_jtl}\n"
|
|
128
|
+
summary
|
|
129
|
+
rescue
|
|
130
|
+
summary.finish!
|
|
131
|
+
raise
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# Creates a new hash tree node for the JMeter test plan.
|
|
137
|
+
#
|
|
138
|
+
# @return [Nokogiri::XML::Node] A new hash tree node.
|
|
139
|
+
def hash_tree
|
|
140
|
+
Nokogiri::XML::Node.new("hashTree", @root)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Attaches a node as the last child of the current node.
|
|
144
|
+
#
|
|
145
|
+
# @param node [Object] The node to attach.
|
|
146
|
+
# @return [Object] The hash tree for the attached node.
|
|
147
|
+
def attach_to_last(node)
|
|
148
|
+
ht = hash_tree
|
|
149
|
+
last_node = @current_node
|
|
150
|
+
last_node << node.doc.children << ht
|
|
151
|
+
ht
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Attaches a node and evaluates the block within its context.
|
|
155
|
+
#
|
|
156
|
+
# @param node [Object] The node to attach.
|
|
157
|
+
# @yield The block to be executed in the node's context.
|
|
158
|
+
def attach_node(node, &block)
|
|
159
|
+
ht = attach_to_last(node)
|
|
160
|
+
previous = @current_node
|
|
161
|
+
@current_node = ht
|
|
162
|
+
instance_exec(&block) if block
|
|
163
|
+
@current_node = previous
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns the Nokogiri XML document.
|
|
167
|
+
#
|
|
168
|
+
# @return [Nokogiri::XML::Document] The XML document for the JMeter test plan.
|
|
169
|
+
def doc
|
|
170
|
+
Nokogiri::XML(@root.to_s, &:noblanks)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Logger instance to log messages to the standard output.
|
|
174
|
+
#
|
|
175
|
+
# @return [Logger] The logger instance, initialized to DEBUG level.
|
|
176
|
+
def logger
|
|
177
|
+
return @logger if @logger
|
|
178
|
+
@logger = Logger.new($stdout)
|
|
179
|
+
@logger.level = Logger::DEBUG
|
|
180
|
+
@logger
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|