jmeter_perf 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
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