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.
Files changed (275) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +19 -0
  4. data/.standard.yml +4 -0
  5. data/CHANGELOG.md +18 -0
  6. data/DSL.md +235 -0
  7. data/README.md +24 -0
  8. data/Rakefile +12 -0
  9. data/example/Gemfile +39 -0
  10. data/example/Gemfile.lock +232 -0
  11. data/example/README.md +3 -0
  12. data/example/Rakefile +6 -0
  13. data/example/app/controllers/application_controller.rb +2 -0
  14. data/example/app/controllers/test_controller.rb +15 -0
  15. data/example/bin/bundle +109 -0
  16. data/example/bin/docker-entrypoint +8 -0
  17. data/example/bin/rails +4 -0
  18. data/example/bin/rake +4 -0
  19. data/example/bin/setup +33 -0
  20. data/example/config/application.rb +44 -0
  21. data/example/config/boot.rb +3 -0
  22. data/example/config/credentials.yml.enc +1 -0
  23. data/example/config/database.yml +25 -0
  24. data/example/config/environment.rb +5 -0
  25. data/example/config/environments/development.rb +64 -0
  26. data/example/config/environments/production.rb +82 -0
  27. data/example/config/environments/test.rb +61 -0
  28. data/example/config/initializers/cors.rb +16 -0
  29. data/example/config/initializers/filter_parameter_logging.rb +8 -0
  30. data/example/config/initializers/inflections.rb +16 -0
  31. data/example/config/locales/en.yml +31 -0
  32. data/example/config/puma.rb +35 -0
  33. data/example/config/routes.rb +5 -0
  34. data/example/config.ru +7 -0
  35. data/example/fast.log +49 -0
  36. data/example/jmeter.log +28 -0
  37. data/example/lib/tasks/test.rake +40 -0
  38. data/example/log/.keep +0 -0
  39. data/example/public/robots.txt +1 -0
  40. data/example/random.log +49 -0
  41. data/example/slow.log +49 -0
  42. data/example/vendor/.keep +0 -0
  43. data/lib/Rakefile +4 -0
  44. data/lib/jmeter_perf/dsl/access_log_sampler.rb +38 -0
  45. data/lib/jmeter_perf/dsl/aggregate_graph.rb +61 -0
  46. data/lib/jmeter_perf/dsl/aggregate_report.rb +61 -0
  47. data/lib/jmeter_perf/dsl/ajp13_sampler.rb +47 -0
  48. data/lib/jmeter_perf/dsl/assertion_results.rb +61 -0
  49. data/lib/jmeter_perf/dsl/bean_shell_assertion.rb +34 -0
  50. data/lib/jmeter_perf/dsl/bean_shell_listener.rb +34 -0
  51. data/lib/jmeter_perf/dsl/bean_shell_postprocessor.rb +34 -0
  52. data/lib/jmeter_perf/dsl/bean_shell_preprocessor.rb +34 -0
  53. data/lib/jmeter_perf/dsl/bean_shell_sampler.rb +34 -0
  54. data/lib/jmeter_perf/dsl/bean_shell_timer.rb +34 -0
  55. data/lib/jmeter_perf/dsl/bsf_assertion.rb +34 -0
  56. data/lib/jmeter_perf/dsl/bsf_listener.rb +34 -0
  57. data/lib/jmeter_perf/dsl/bsf_postprocessor.rb +34 -0
  58. data/lib/jmeter_perf/dsl/bsf_preprocessor.rb +34 -0
  59. data/lib/jmeter_perf/dsl/bsf_sampler.rb +34 -0
  60. data/lib/jmeter_perf/dsl/bsf_timer.rb +34 -0
  61. data/lib/jmeter_perf/dsl/compare_assertion.rb +33 -0
  62. data/lib/jmeter_perf/dsl/comparison_assertion_visualizer.rb +61 -0
  63. data/lib/jmeter_perf/dsl/constant_throughput_timer.rb +32 -0
  64. data/lib/jmeter_perf/dsl/constant_timer.rb +31 -0
  65. data/lib/jmeter_perf/dsl/counter.rb +37 -0
  66. data/lib/jmeter_perf/dsl/css_jquery_extractor.rb +37 -0
  67. data/lib/jmeter_perf/dsl/csv_data_set_config.rb +39 -0
  68. data/lib/jmeter_perf/dsl/debug_postprocessor.rb +34 -0
  69. data/lib/jmeter_perf/dsl/debug_sampler.rb +33 -0
  70. data/lib/jmeter_perf/dsl/distribution_graphalpha.rb +61 -0
  71. data/lib/jmeter_perf/dsl/duration_assertion.rb +31 -0
  72. data/lib/jmeter_perf/dsl/for_each_controller.rb +33 -0
  73. data/lib/jmeter_perf/dsl/ftp_request.rb +40 -0
  74. data/lib/jmeter_perf/dsl/ftp_request_defaults.rb +38 -0
  75. data/lib/jmeter_perf/dsl/gaussian_random_timer.rb +32 -0
  76. data/lib/jmeter_perf/dsl/generate_summary_results.rb +29 -0
  77. data/lib/jmeter_perf/dsl/graph_results.rb +61 -0
  78. data/lib/jmeter_perf/dsl/html_assertion.rb +36 -0
  79. data/lib/jmeter_perf/dsl/html_link_parser.rb +29 -0
  80. data/lib/jmeter_perf/dsl/html_parameter_mask.rb +38 -0
  81. data/lib/jmeter_perf/dsl/http_authorization_manager.rb +39 -0
  82. data/lib/jmeter_perf/dsl/http_cache_manager.rb +32 -0
  83. data/lib/jmeter_perf/dsl/http_cookie_manager.rb +34 -0
  84. data/lib/jmeter_perf/dsl/http_header_manager.rb +36 -0
  85. data/lib/jmeter_perf/dsl/http_request.rb +47 -0
  86. data/lib/jmeter_perf/dsl/http_request_defaults.rb +53 -0
  87. data/lib/jmeter_perf/dsl/http_url_rewriting_modifier.rb +36 -0
  88. data/lib/jmeter_perf/dsl/if_controller.rb +33 -0
  89. data/lib/jmeter_perf/dsl/include_controller.rb +31 -0
  90. data/lib/jmeter_perf/dsl/j_unit_request.rb +43 -0
  91. data/lib/jmeter_perf/dsl/java_request.rb +75 -0
  92. data/lib/jmeter_perf/dsl/java_request_defaults.rb +75 -0
  93. data/lib/jmeter_perf/dsl/jdbc_connection_configuration.rb +43 -0
  94. data/lib/jmeter_perf/dsl/jdbc_postprocessor.rb +39 -0
  95. data/lib/jmeter_perf/dsl/jdbc_preprocessor.rb +39 -0
  96. data/lib/jmeter_perf/dsl/jdbc_request.rb +39 -0
  97. data/lib/jmeter_perf/dsl/jms_pointto_point.rb +47 -0
  98. data/lib/jmeter_perf/dsl/jms_publisher.rb +49 -0
  99. data/lib/jmeter_perf/dsl/jms_subscriber.rb +41 -0
  100. data/lib/jmeter_perf/dsl/json_path_postprocessor.rb +33 -0
  101. data/lib/jmeter_perf/dsl/jsr223_assertion.rb +35 -0
  102. data/lib/jmeter_perf/dsl/jsr223_listener.rb +35 -0
  103. data/lib/jmeter_perf/dsl/jsr223_postprocessor.rb +35 -0
  104. data/lib/jmeter_perf/dsl/jsr223_preprocessor.rb +35 -0
  105. data/lib/jmeter_perf/dsl/jsr223_sampler.rb +35 -0
  106. data/lib/jmeter_perf/dsl/jsr223_timer.rb +35 -0
  107. data/lib/jmeter_perf/dsl/keystore_configuration.rb +34 -0
  108. data/lib/jmeter_perf/dsl/ldap_extended_request.rb +48 -0
  109. data/lib/jmeter_perf/dsl/ldap_extended_request_defaults.rb +48 -0
  110. data/lib/jmeter_perf/dsl/ldap_request.rb +41 -0
  111. data/lib/jmeter_perf/dsl/ldap_request_defaults.rb +45 -0
  112. data/lib/jmeter_perf/dsl/login_config_element.rb +32 -0
  113. data/lib/jmeter_perf/dsl/loop_controller.rb +32 -0
  114. data/lib/jmeter_perf/dsl/mail_reader_sampler.rb +43 -0
  115. data/lib/jmeter_perf/dsl/mailer_visualizer.rb +70 -0
  116. data/lib/jmeter_perf/dsl/md5_hex_assertion.rb +31 -0
  117. data/lib/jmeter_perf/dsl/module_controller.rb +31 -0
  118. data/lib/jmeter_perf/dsl/monitor_results.rb +61 -0
  119. data/lib/jmeter_perf/dsl/once_only_controller.rb +29 -0
  120. data/lib/jmeter_perf/dsl/os_process_sampler.rb +40 -0
  121. data/lib/jmeter_perf/dsl/poisson_random_timer.rb +32 -0
  122. data/lib/jmeter_perf/dsl/random_controller.rb +31 -0
  123. data/lib/jmeter_perf/dsl/random_order_controller.rb +29 -0
  124. data/lib/jmeter_perf/dsl/random_variable.rb +36 -0
  125. data/lib/jmeter_perf/dsl/recording_controller.rb +29 -0
  126. data/lib/jmeter_perf/dsl/reg_ex_user_parameters.rb +33 -0
  127. data/lib/jmeter_perf/dsl/regular_expression_extractor.rb +38 -0
  128. data/lib/jmeter_perf/dsl/response_assertion.rb +37 -0
  129. data/lib/jmeter_perf/dsl/response_time_graph.rb +61 -0
  130. data/lib/jmeter_perf/dsl/result_status_action_handler.rb +31 -0
  131. data/lib/jmeter_perf/dsl/runtime_controller.rb +31 -0
  132. data/lib/jmeter_perf/dsl/save_responses_to_a_file.rb +35 -0
  133. data/lib/jmeter_perf/dsl/simple_config_element.rb +29 -0
  134. data/lib/jmeter_perf/dsl/simple_controller.rb +29 -0
  135. data/lib/jmeter_perf/dsl/simple_data_writer.rb +61 -0
  136. data/lib/jmeter_perf/dsl/smime_assertion.rb +41 -0
  137. data/lib/jmeter_perf/dsl/smtp_sampler.rb +57 -0
  138. data/lib/jmeter_perf/dsl/soap_xml_rpc_request.rb +39 -0
  139. data/lib/jmeter_perf/dsl/spline_visualizer.rb +61 -0
  140. data/lib/jmeter_perf/dsl/summary_report.rb +61 -0
  141. data/lib/jmeter_perf/dsl/switch_controller.rb +31 -0
  142. data/lib/jmeter_perf/dsl/synchronizing_timer.rb +32 -0
  143. data/lib/jmeter_perf/dsl/tcp_sampler.rb +39 -0
  144. data/lib/jmeter_perf/dsl/tcp_sampler_config.rb +37 -0
  145. data/lib/jmeter_perf/dsl/test_action.rb +33 -0
  146. data/lib/jmeter_perf/dsl/test_fragment.rb +29 -0
  147. data/lib/jmeter_perf/dsl/test_plan.rb +37 -0
  148. data/lib/jmeter_perf/dsl/thread_group.rb +43 -0
  149. data/lib/jmeter_perf/dsl/throughput_controller.rb +38 -0
  150. data/lib/jmeter_perf/dsl/transaction_controller.rb +32 -0
  151. data/lib/jmeter_perf/dsl/uniform_random_timer.rb +32 -0
  152. data/lib/jmeter_perf/dsl/user_defined_variables.rb +39 -0
  153. data/lib/jmeter_perf/dsl/user_parameters.rb +36 -0
  154. data/lib/jmeter_perf/dsl/view_results_in_table.rb +61 -0
  155. data/lib/jmeter_perf/dsl/view_results_tree.rb +61 -0
  156. data/lib/jmeter_perf/dsl/while_controller.rb +31 -0
  157. data/lib/jmeter_perf/dsl/x_path_assertion.rb +37 -0
  158. data/lib/jmeter_perf/dsl/x_path_extractor.rb +37 -0
  159. data/lib/jmeter_perf/dsl/xml_assertion.rb +29 -0
  160. data/lib/jmeter_perf/dsl/xml_schema_assertion.rb +31 -0
  161. data/lib/jmeter_perf/extend/assertions/response_assertion.rb +38 -0
  162. data/lib/jmeter_perf/extend/config_elements/header_manager.rb +13 -0
  163. data/lib/jmeter_perf/extend/config_elements/http_cache_manager.rb +12 -0
  164. data/lib/jmeter_perf/extend/config_elements/http_cookie_manager.rb +39 -0
  165. data/lib/jmeter_perf/extend/config_elements/http_request_defaults.rb +55 -0
  166. data/lib/jmeter_perf/extend/config_elements/user_defined_variables.rb +13 -0
  167. data/lib/jmeter_perf/extend/config_elements/user_parameters.rb +31 -0
  168. data/lib/jmeter_perf/extend/controllers/foreach_controller.rb +31 -0
  169. data/lib/jmeter_perf/extend/controllers/loop_controller.rb +11 -0
  170. data/lib/jmeter_perf/extend/controllers/module_controller.rb +26 -0
  171. data/lib/jmeter_perf/extend/controllers/throughput_controller.rb +15 -0
  172. data/lib/jmeter_perf/extend/controllers/transaction_controller.rb +14 -0
  173. data/lib/jmeter_perf/extend/misc/exists.rb +13 -0
  174. data/lib/jmeter_perf/extend/misc/rsync.rb +24 -0
  175. data/lib/jmeter_perf/extend/misc/uuid.rb +12 -0
  176. data/lib/jmeter_perf/extend/misc/with_helpers.rb +27 -0
  177. data/lib/jmeter_perf/extend/plugins/jmeter_plugins.rb +124 -0
  178. data/lib/jmeter_perf/extend/processors/extract.rb +27 -0
  179. data/lib/jmeter_perf/extend/processors/regular_expression_extractor.rb +27 -0
  180. data/lib/jmeter_perf/extend/samplers/http_request.rb +66 -0
  181. data/lib/jmeter_perf/extend/samplers/jms_pointtopoint.rb +23 -0
  182. data/lib/jmeter_perf/extend/samplers/soapxmlrpc_request.rb +10 -0
  183. data/lib/jmeter_perf/extend/threads/thread_group.rb +19 -0
  184. data/lib/jmeter_perf/extend/timers/constant_throughput_timer.rb +11 -0
  185. data/lib/jmeter_perf/extend/timers/random_timer.rb +14 -0
  186. data/lib/jmeter_perf/helpers/dsl_generator.rb +157 -0
  187. data/lib/jmeter_perf/helpers/fallback_content_proxy.rb +96 -0
  188. data/lib/jmeter_perf/helpers/helper.rb +63 -0
  189. data/lib/jmeter_perf/helpers/parser.rb +143 -0
  190. data/lib/jmeter_perf/helpers/running_statistics.rb +62 -0
  191. data/lib/jmeter_perf/helpers/string.rb +60 -0
  192. data/lib/jmeter_perf/helpers/user-agents.rb +42 -0
  193. data/lib/jmeter_perf/plugins/active_threads_over_time.rb +59 -0
  194. data/lib/jmeter_perf/plugins/composite_graph.rb +77 -0
  195. data/lib/jmeter_perf/plugins/console_status_logger.rb +19 -0
  196. data/lib/jmeter_perf/plugins/dummy_sampler.rb +30 -0
  197. data/lib/jmeter_perf/plugins/jmx_collector.rb +74 -0
  198. data/lib/jmeter_perf/plugins/json_path_assertion.rb +23 -0
  199. data/lib/jmeter_perf/plugins/json_path_extractor.rb +22 -0
  200. data/lib/jmeter_perf/plugins/latencies_over_time.rb +53 -0
  201. data/lib/jmeter_perf/plugins/loadosophia_uploader.rb +66 -0
  202. data/lib/jmeter_perf/plugins/perfmon_collector.rb +87 -0
  203. data/lib/jmeter_perf/plugins/redis_data_set.rb +43 -0
  204. data/lib/jmeter_perf/plugins/response_codes_per_second.rb +53 -0
  205. data/lib/jmeter_perf/plugins/response_times_distribution.rb +53 -0
  206. data/lib/jmeter_perf/plugins/response_times_over_time.rb +53 -0
  207. data/lib/jmeter_perf/plugins/response_times_percentiles.rb +54 -0
  208. data/lib/jmeter_perf/plugins/stepping_thread_group.rb +34 -0
  209. data/lib/jmeter_perf/plugins/transactions_per_second.rb +53 -0
  210. data/lib/jmeter_perf/plugins/ultimate_thread_group.rb +28 -0
  211. data/lib/jmeter_perf/plugins/variable_throughput_timer.rb +35 -0
  212. data/lib/jmeter_perf/report/comparator.rb +258 -0
  213. data/lib/jmeter_perf/report/summary.rb +268 -0
  214. data/lib/jmeter_perf/tasks/dsl.rake +19 -0
  215. data/lib/jmeter_perf/version.rb +5 -0
  216. data/lib/jmeter_perf/views/report_template.html.erb +114 -0
  217. data/lib/jmeter_perf.rb +183 -0
  218. data/lib/specifications/idl.xml +1494 -0
  219. data/sig/jmeter_perf.rbs +195 -0
  220. data/sorbet/config +5 -0
  221. data/sorbet/rbi/annotations/.gitattributes +1 -0
  222. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  223. data/sorbet/rbi/gems/.gitattributes +1 -0
  224. data/sorbet/rbi/gems/ast@2.4.2.rbi +585 -0
  225. data/sorbet/rbi/gems/bump@0.10.0.rbi +169 -0
  226. data/sorbet/rbi/gems/byebug@11.1.3.rbi +3607 -0
  227. data/sorbet/rbi/gems/coderay@1.1.3.rbi +3427 -0
  228. data/sorbet/rbi/gems/diff-lcs@1.5.1.rbi +1131 -0
  229. data/sorbet/rbi/gems/docile@1.4.1.rbi +377 -0
  230. data/sorbet/rbi/gems/erubi@1.13.0.rbi +150 -0
  231. data/sorbet/rbi/gems/json@2.7.2.rbi +1562 -0
  232. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14238 -0
  233. data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +240 -0
  234. data/sorbet/rbi/gems/method_source@1.1.0.rbi +304 -0
  235. data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
  236. data/sorbet/rbi/gems/nokogiri@1.16.7.rbi +7311 -0
  237. data/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
  238. data/sorbet/rbi/gems/parser@3.3.5.0.rbi +5519 -0
  239. data/sorbet/rbi/gems/prism@1.2.0.rbi +39085 -0
  240. data/sorbet/rbi/gems/pry-byebug@3.10.1.rbi +1151 -0
  241. data/sorbet/rbi/gems/pry@0.14.2.rbi +10076 -0
  242. data/sorbet/rbi/gems/racc@1.8.1.rbi +162 -0
  243. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +403 -0
  244. data/sorbet/rbi/gems/rake@13.2.1.rbi +3074 -0
  245. data/sorbet/rbi/gems/rbi@0.2.1.rbi +4535 -0
  246. data/sorbet/rbi/gems/rbtree3@0.7.1.rbi +9 -0
  247. data/sorbet/rbi/gems/regexp_parser@2.9.2.rbi +3772 -0
  248. data/sorbet/rbi/gems/rexml@3.3.8.rbi +4858 -0
  249. data/sorbet/rbi/gems/rspec-core@3.13.1.rbi +11132 -0
  250. data/sorbet/rbi/gems/rspec-expectations@3.13.3.rbi +8183 -0
  251. data/sorbet/rbi/gems/rspec-mocks@3.13.1.rbi +5341 -0
  252. data/sorbet/rbi/gems/rspec-support@3.13.1.rbi +1630 -0
  253. data/sorbet/rbi/gems/rspec@3.13.0.rbi +83 -0
  254. data/sorbet/rbi/gems/rubocop-ast@1.32.3.rbi +7054 -0
  255. data/sorbet/rbi/gems/rubocop-performance@1.21.1.rbi +9 -0
  256. data/sorbet/rbi/gems/rubocop@1.65.1.rbi +58182 -0
  257. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1318 -0
  258. data/sorbet/rbi/gems/simplecov-html@0.13.1.rbi +225 -0
  259. data/sorbet/rbi/gems/simplecov@0.22.0.rbi +2149 -0
  260. data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +9 -0
  261. data/sorbet/rbi/gems/spoom@1.5.0.rbi +4932 -0
  262. data/sorbet/rbi/gems/standard-custom@1.0.2.rbi +9 -0
  263. data/sorbet/rbi/gems/standard-performance@1.4.0.rbi +9 -0
  264. data/sorbet/rbi/gems/standard@1.40.0.rbi +926 -0
  265. data/sorbet/rbi/gems/tapioca@0.16.3.rbi +3596 -0
  266. data/sorbet/rbi/gems/tdigest@0.2.1.rbi +170 -0
  267. data/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
  268. data/sorbet/rbi/gems/unicode-display_width@2.6.0.rbi +66 -0
  269. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
  270. data/sorbet/rbi/gems/yard@0.9.37.rbi +18379 -0
  271. data/sorbet/rbi/todo.rbi +31 -0
  272. data/sorbet/tapioca/config.yml +13 -0
  273. data/sorbet/tapioca/require.rb +15 -0
  274. data/tasks/dsl.rake +22 -0
  275. 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JmeterPerf
4
+ VERSION = "1.0.9"
5
+ 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>
@@ -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