jmeter_perf 1.0.10 → 1.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08f52576a21a7846d4b8c9e9a64a515d41eca60573109bbc6e824e799d666ad2'
4
- data.tar.gz: bf3ef28bf092d0ebde1dfd073349a8f43677b588b730f0e8d6303ce84975cdfc
3
+ metadata.gz: 600531edf66d0d72c64c6015327f9b63db03ab11357e28758a7250aead0d9106
4
+ data.tar.gz: f254970ad87fb7850e4989d25c3f6437cb037614a76cd5c145b6c3bb5db6b4a3
5
5
  SHA512:
6
- metadata.gz: 906d3f98be9a3286557f094a6cb65a6cb0e154e65b8535439ca274435ca654248ac627a46caeb9439b6a336055334bbab62acb7d2c627b9949aeeb8a07929247
7
- data.tar.gz: 10b89dc579ed6b9cbec7f8e413ce95d95b51cc4622cf779293949cf530b94e84d2580dbd30b126372e1ee01c2de364d6839e2d5feb19feaf5b5a9a958d5e665c
6
+ metadata.gz: 14c69cb7eac86ce588226a5c69a4b791e7d4f9015034c2314f9c13daab0a5dbeab5feeaaaaf6f58fe9be22c3216ac99e94f9d0e718cc1027ea4aa0d2e43e43a7
7
+ data.tar.gz: 2608a6d4db406a42a9bbaf2874e2cebfdb0768b62100e5515eb68a098683fc191410632aa9c314c0b17a8884405fc314499473d7335dc4d4177b3c56e8f3abfe
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
- --format documentation
1
+ --profile
2
+ --format progress
2
3
  --color
3
- --require spec_helper
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # JmeterPerf
2
2
  Dynamically generate JMeter test plans, run performance tests, generate reports, compare reports and more using JMeter and Ruby.
3
+ Inspired by the now archived/defunct [ruby-jmeter](https://github.com/flood-io/ruby-jmeter).
3
4
 
4
5
  ## Installation
5
6
 
@@ -17,7 +18,11 @@ Check out the Wiki! https://github.com/jlurena/jmeter_perf/wiki
17
18
 
18
19
  ## Development
19
20
 
20
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
21
+ After checking out the repo, run `bin/setup` to install dependencies.
22
+ Run `rake` to run the linter and tests.
23
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
+
25
+ Additionally, there is an `example` Rails Application you can play with.
21
26
 
22
27
  ## Contributing
23
28
 
data/example/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- jmeter_perf (1.0.6)
4
+ jmeter_perf (1.1.0)
5
5
  nokogiri (~> 1.16, >= 1.16.7)
6
6
  tdigest (~> 0.2.1)
7
7
 
@@ -25,8 +25,21 @@ module JmeterPerf
25
25
  huge: 2.0 # huge
26
26
  }
27
27
 
28
- # Valid effect size directions
29
- EFFECT_SIZE_DIRECTION = %i[positive negative both]
28
+ COMPARISON_REPORT_HEADER = [
29
+ "Label",
30
+ "Total Requests",
31
+ "Total Elapsed Time",
32
+ "RPM",
33
+ "Errors",
34
+ "Error %",
35
+ "Min",
36
+ "Max",
37
+ "Avg",
38
+ "SD",
39
+ "P10",
40
+ "P50",
41
+ "P95"
42
+ ]
30
43
 
31
44
  # Initializes a Comparator instance to compare two reports.
32
45
  #
@@ -40,51 +53,80 @@ module JmeterPerf
40
53
  compare_reports!
41
54
  end
42
55
 
43
- # Checks if the comparison passes based on Cohen's D and effect size.
56
+ # Checks if the comparison passes based on Cohen's D and effect size threshold.
44
57
  # @note If no Cohen's D limit is provided, the `effect_size` threshold is used.
45
- # @param cohens_d_limit [Float, nil] optional limit for Cohen's D (default: nil)
58
+ # @note Positive effect size indicates an increase in performance and is considered a pass.
59
+ # @param cohens_d_limit [Float, nil] optional negative limit for Cohen's D
46
60
  # @param effect_size [Symbol] the desired effect size threshold (default: :vsmall).
47
61
  # See {JmeterPerf::Report::Comparator::EFFECT_SIZE_LIMITS} for options.
48
- # @param direction [Symbol] the direction of comparison, e.g., :positive (default: :both)
49
- # See {JmeterPerf::Report::Comparator::EFFECT_SIZE_DIRECTION} for options.
50
- # @raise [ArgumentError] if the effect size or direction is invalid
62
+ # @raise [ArgumentError] if the effect size is invalid
51
63
  # @return [Boolean] true if comparison meets the criteria
52
- def pass?(cohens_d_limit: nil, effect_size: :vsmall, direction: :both)
64
+ def pass?(cohens_d_limit: nil, effect_size: :vsmall)
53
65
  limit = cohens_d_limit || EFFECT_SIZE_LIMITS[effect_size]
54
66
  raise ArgumentError, "Invalid effect size: #{effect_size}" unless limit
55
-
56
- raise ArgumentError, "Invalid direction: #{direction}" unless EFFECT_SIZE_DIRECTION.include?(direction)
57
-
58
- case direction
59
- when :positive
60
- cohens_d >= limit
61
- when :negative
62
- cohens_d <= -limit
63
- when :both
64
- cohens_d >= limit || !(cohens_d <= -limit)
65
- end
67
+ cohens_d >= -limit.abs
66
68
  end
67
69
 
68
70
  # Generates comparison reports in specified formats.
69
71
  #
70
72
  # @param output_dir [String] the directory for output files (default: ".")
71
- # @param output_format [Symbol] the format for the report, e.g., :html, :csv (default: :all)
73
+ # @param output_format [Symbol] the format for the report, e.g., :html, :csv, :stdout (default: :all)
72
74
  # @raise [ArgumentError] if the output format is invalid
73
75
  # @return [void]
74
76
  def generate_reports(output_dir: ".", output_format: :all)
75
- generator = Generator.new(self, [@base_report, @test_report])
76
-
77
77
  case output_format
78
78
  when :all
79
- generator.generate_report(File.join(output_dir, "#{@name}_comparison_report.html"), :html)
80
- generator.generate_report(File.join(output_dir, "#{@name}_comparison_report.csv"), :csv)
81
- when :html, :csv
82
- generator.generate_report(File.join(output_dir, "#{@name}_comparison_report.#{output_format}"), output_format)
79
+ generate_html_report(File.join(output_dir, "#{@name}_comparison_report.html"))
80
+ generate_csv_report(File.join(output_dir, "#{@name}_comparison_report.csv"))
81
+ print_comparison
82
+ when :html
83
+ generate_html_report(File.join(output_dir, "#{@name}_comparison_report.html"))
84
+ when :csv
85
+ generate_csv_report(File.join(output_dir, "#{@name}_comparison_report.csv"))
86
+ when :stdout
87
+ print_comparison
83
88
  else
84
89
  raise ArgumentError, "Invalid output format: #{output_format}"
85
90
  end
86
91
  end
87
92
 
93
+ def to_s
94
+ report_text = "Comparison Report\n"
95
+ report_text << "Cohen's D: #{@cohens_d}\n"
96
+ report_text << "Human Rating: #{@human_rating}\n"
97
+ report_text << "-" * 135 + "\n"
98
+
99
+ header_format = "%-15s %-17s %-18s %-8s %-8s %-9s %-7s %-7s %-8s %-8s %-8s %-8s %-8s\n"
100
+ row_format = "%-15s %-17d %-18d %-8.2f %-8d %-9.2f %-7d %-7d %-8.2f %-8.2f %-8.2f %-8.2f %-8.2f\n"
101
+
102
+ report_text << sprintf(header_format, *COMPARISON_REPORT_HEADER)
103
+ report_text << "-" * 135 + "\n"
104
+
105
+ [@base_report, @test_report].each_with_index do |report, index|
106
+ report_text << sprintf(row_format,
107
+ (index == 0) ? "Base Metric" : "Test Metric",
108
+ report.total_requests,
109
+ report.total_run_time,
110
+ report.rpm,
111
+ report.total_errors,
112
+ report.error_percentage,
113
+ report.min,
114
+ report.max,
115
+ report.avg,
116
+ report.std,
117
+ report.p10,
118
+ report.p50,
119
+ report.p95)
120
+ end
121
+
122
+ report_text << "-" * 135 + "\n"
123
+ report_text
124
+ end
125
+
126
+ def print_comparison
127
+ puts self
128
+ end
129
+
88
130
  private
89
131
 
90
132
  # Calculates Cohen's D and T-statistic between the two reports.
@@ -151,108 +193,43 @@ module JmeterPerf
151
193
  @human_rating = "#{s_mag} #{s_dir}"
152
194
  end
153
195
 
154
- # Generator is responsible for generating comparison reports in HTML and CSV formats.
155
- class Generator
156
- # Initializes a Generator instance to handle report generation.
157
- #
158
- # @param comparator [Comparator] the comparator instance
159
- # @param reports [Array<Summary>] an array of performance reports
160
- def initialize(comparator, reports)
161
- @comparator = comparator
162
- @reports = reports
163
- end
164
-
165
- # Generates a report in the specified format at the given path.
166
- #
167
- # @param output_path [String] the path to save the generated report
168
- # @param output_format [Symbol] the format for the report, e.g., :html or :csv
169
- # @return [void]
170
- def generate_report(output_path, output_format)
171
- case output_format
172
- when :html
173
- generate_html_report(output_path)
174
- when :csv
175
- generate_csv_report(output_path)
176
- else
177
- print_report(output_path)
178
- end
179
- end
180
-
181
- # Generates an HTML report.
182
- #
183
- # @param output_path [String] the path to save the HTML report
184
- # @return [void]
185
- def generate_html_report(output_path)
186
- template_path = File.join(__dir__, "..", "views", "report_template.html.erb")
187
- template = File.read(template_path)
188
- result = ERB.new(template).result(binding)
189
- File.write(output_path, result)
190
- end
191
-
192
- # Generates a CSV report.
193
- #
194
- # @param output_path [String] the path to save the CSV report
195
- # @return [void]
196
- def generate_csv_report(output_path)
197
- CSV.open(output_path, "wb") do |csv|
198
- csv << ["Label", "Total Requests", "Total Elapsed Time", "RPM", "Errors", "Error %", "Min", "Max", "Avg", "SD", "P10", "P50", "P95"]
199
- @reports.each_with_index do |report, index|
200
- csv << [
201
- (index == 0) ? "Base Metric" : "Test Metric",
202
- report.total_requests,
203
- report.total_elapsed_time,
204
- sprintf("%.2f", report.rpm),
205
- report.total_errors,
206
- sprintf("%.2f", report.error_percentage),
207
- report.min,
208
- report.max,
209
- sprintf("%.2f", report.avg),
210
- sprintf("%.2f", report.std),
211
- sprintf("%.2f", report.p10),
212
- sprintf("%.2f", report.p50),
213
- sprintf("%.2f", report.p95)
214
- ]
215
- end
216
- end
217
- end
196
+ # Generates an HTML report.
197
+ #
198
+ # @param output_path [String] the path to save the HTML report
199
+ # @return [void]
200
+ def generate_html_report(output_path)
201
+ template_path = File.join(__dir__, "..", "views", "report_template.html.erb")
202
+ template = File.read(template_path)
203
+ result = ERB.new(template).result(binding)
204
+ File.write(output_path, result)
205
+ end
218
206
 
219
- # Prints a plain text version of the report to standard output.
220
- #
221
- # @param output_path [String] the path for plain text output
222
- # @return [void]
223
- def print_report(output_path)
224
- report_text = "Comparison Report\n\n"
225
- report_text << format_line(["Label", "Requests", "Errors", "Error %", "Min", "Median", "Avg", "Max", "Std", "P10", "P50", "P95"])
226
- report_text << "-" * 90 + "\n"
227
- @reports.each_with_index do |report, index|
228
- report_text << format_line([
207
+ # Generates a CSV report.
208
+ #
209
+ # @param output_path [String] the path to save the CSV report
210
+ # @return [void]
211
+ def generate_csv_report(output_path)
212
+ CSV.open(output_path, "wb") do |csv|
213
+ csv << COMPARISON_REPORT_HEADER
214
+ [@base_report, @test_report].each_with_index do |report, index|
215
+ csv << [
229
216
  (index == 0) ? "Base Metric" : "Test Metric",
230
217
  report.total_requests,
218
+ report.total_run_time,
219
+ sprintf("%.2f", report.rpm),
231
220
  report.total_errors,
232
221
  sprintf("%.2f", report.error_percentage),
233
222
  report.min,
234
- report.median,
235
- sprintf("%.2f", report.avg),
236
223
  report.max,
224
+ sprintf("%.2f", report.avg),
237
225
  sprintf("%.2f", report.std),
238
226
  sprintf("%.2f", report.p10),
239
227
  sprintf("%.2f", report.p50),
240
228
  sprintf("%.2f", report.p95)
241
- ])
229
+ ]
242
230
  end
243
- puts report_text
244
- end
245
-
246
- # Formats a line for plain text output.
247
- #
248
- # @param values [Array<String, Numeric>] the values to format
249
- # @return [String] the formatted line
250
- def format_line(values)
251
- values.map { |v| v.to_s.ljust(10) }.join(" ") + "\n"
252
231
  end
253
232
  end
254
-
255
- private_constant :Generator
256
233
  end
257
234
  end
258
235
  end
@@ -32,8 +32,6 @@ module JmeterPerf
32
32
  # @return [Float] the standard deviation of response times
33
33
  # @!attribute [rw] total_bytes
34
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
35
  # @!attribute [rw] total_errors
38
36
  # @return [Integer] the total number of errors encountered
39
37
  # @!attribute [rw] total_latency
@@ -88,7 +86,6 @@ module JmeterPerf
88
86
  "Standard Deviation" => :standard_deviation,
89
87
  "Total Run Time" => :total_run_time,
90
88
  "Total Bytes" => :total_bytes,
91
- "Total Elapsed Time" => :total_elapsed_time,
92
89
  "Total Errors" => :total_errors,
93
90
  "Total Latency" => :total_latency,
94
91
  "Total Requests" => :total_requests,
@@ -135,7 +132,7 @@ module JmeterPerf
135
132
  # @param file_path [String] the file path of the performance file to summarize. Either a JTL or CSV file.
136
133
  # @param name [String, nil] an optional name for the summary, derived from the file path if not provided (default: nil)
137
134
  # @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)
135
+ def initialize(file_path:, name: nil, jtl_read_timeout: 30)
139
136
  @name = name || file_path.to_s.tr("/", "_")
140
137
  @jtl_read_timeout = jtl_read_timeout
141
138
  @finished = false
@@ -145,7 +142,6 @@ module JmeterPerf
145
142
  @min = 1_000_000
146
143
  @response_codes = Hash.new { |h, k| h[k.to_s] = 0 }
147
144
  @total_bytes = 0
148
- @total_elapsed_time = 0
149
145
  @total_errors = 0
150
146
  @total_latency = 0
151
147
  @total_requests = 0
@@ -158,12 +154,12 @@ module JmeterPerf
158
154
  @end_time = nil
159
155
  end
160
156
 
161
- # Marks the summary as finished, allowing any pending asynchronous operations to complete.
157
+ # Marks the summary as finished and joins the processing thread.
162
158
  #
163
159
  # @return [void]
164
160
  def finish!
165
161
  @finished = true
166
- @processing_jtl_thread.join if @processing_jtl_thread&.alive?
162
+ @processing_jtl_thread&.join
167
163
  end
168
164
 
169
165
  # Generates a CSV report with the given output file.
@@ -191,23 +187,28 @@ module JmeterPerf
191
187
  end
192
188
 
193
189
  # Starts streaming and processing JTL file content asynchronously.
194
- #
195
- # @return [Thread] a thread that handles the asynchronous file streaming and parsing
190
+ # @note Once streaming, in order to finish processing, call `finish!` otherwise it will continue indefinitely.
191
+ # @return [void]
196
192
  def stream_jtl_async
197
193
  @processing_jtl_thread = Thread.new do
198
- sleep 0.1 until File.exist?(@file_path) # Wait for the file to be created
194
+ Timeout.timeout(@jtl_read_timeout, nil, "Timed out attempting to open JTL File #{@file_path}") do
195
+ sleep 0.1 until File.exist?(@file_path) # Wait for the file to be created
196
+ end
199
197
 
200
198
  File.open(@file_path, "r") do |file|
201
- file.seek(0, IO::SEEK_END)
202
199
  until @finished && file.eof?
203
200
  line = file.gets
204
- next unless line # Skip if no line was read
205
201
 
206
- # Process only if the line is complete (ends with a newline)
202
+ # Skip if the line is nil. This can happen if not @finished, and we are at EOF
203
+ next if line.nil?
204
+ # Process only if the line is complete. JMeter always finishes with a newline
207
205
  read_until_complete_line(file, line)
208
206
  end
209
207
  end
210
208
  end
209
+
210
+ @processing_jtl_thread.abort_on_exception = true
211
+ nil
211
212
  end
212
213
 
213
214
  # Summarizes the collected data by calculating statistical metrics and error rates.
@@ -225,17 +226,19 @@ module JmeterPerf
225
226
  private
226
227
 
227
228
  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")
229
+ lineno = file.lineno
230
+ return if lineno == 1 # Skip the header row
231
+ Timeout.timeout(@jtl_read_timeout, nil, "Timed out processing line #{lineno}") do
232
+ # If finished and eof but no newline: Means processing was interrupted
233
+ # JMeter always finishes with a new line in the JTL file
234
+ until line.end_with?("\n") || (file.eof? && @finished)
231
235
  sleep 0.1
232
236
  line += file.gets.to_s
233
237
  end
234
238
  end
235
239
  parse_csv_row(line)
236
240
  rescue Timeout::Error
237
- puts "Timeout waiting for line to complete: #{line}"
238
- raise
241
+ raise Timeout::Error, "Timed out reading JTL file at line #{lineno}"
239
242
  rescue CSV::MalformedCSVError
240
243
  @csv_error_lines << file.lineno
241
244
  end
@@ -253,7 +256,6 @@ module JmeterPerf
253
256
  # Continue with processing the row as before...
254
257
  @running_statistics_helper.add_number(elapsed)
255
258
  @total_requests += 1
256
- @total_elapsed_time += elapsed
257
259
  @response_codes[line_item.fetch(:responseCode)] += 1
258
260
  @total_errors += (line_item.fetch(:success) == "true") ? 0 : 1
259
261
  @total_bytes += line_item.fetch(:bytes, 0).to_i
@@ -13,26 +13,20 @@ RSpec::Matchers.define :pass_performance_test do
13
13
  @effect_size = effect_size
14
14
  end
15
15
 
16
- chain :with_direction do |direction|
17
- @direction = direction
18
- end
19
-
20
16
  chain :with_cohen_d_limit do |limit|
21
17
  @cohen_limit = limit
22
18
  end
23
19
 
24
20
  chain :with do |options|
25
- @effect_size = options[:effect_size]
26
- @direction = options[:direction]
27
21
  @cohen_limit = options[:cohen_limit]
22
+ @effect_size = options[:effect_size]
28
23
  end
29
24
 
30
25
  match do |comparator|
31
26
  if comparator.is_a?(JmeterPerf::Report::Comparator)
32
27
  comparator.pass?(
33
28
  cohens_d_limit: @cohen_limit || nil,
34
- effect_size: @effect_size || :vsmall,
35
- direction: @direction || :both
29
+ effect_size: @effect_size || :vsmall
36
30
  )
37
31
  else
38
32
  false
@@ -41,7 +35,7 @@ RSpec::Matchers.define :pass_performance_test do
41
35
 
42
36
  failure_message do |comparator|
43
37
  if comparator.is_a?(JmeterPerf::Report::Comparator)
44
- "#{comparator.name} failed: #{comparator.cohens_d} | #{comparator.human_rating}"
38
+ "Performance Test Failed\n#{comparator}"
45
39
  else
46
40
  "#{comparator.class.name} is not a valid comparator"
47
41
  end
@@ -49,7 +43,7 @@ RSpec::Matchers.define :pass_performance_test do
49
43
 
50
44
  failure_message_when_negated do |comparator|
51
45
  if comparator.is_a?(JmeterPerf::Report::Comparator)
52
- "#{comparator.name} passed: #{comparator.cohens_d} | #{comparator.human_rating}"
46
+ "Performance Test Passed\n#{comparator}"
53
47
  else
54
48
  "#{comparator.class.name} is not a valid comparator"
55
49
  end
@@ -1 +1 @@
1
- Dir.glob(File.join(__dir__, "rspec_matchers.rb")).each { |f| require_relative f }
1
+ Dir.glob(File.join(__dir__, "rspec_matchers/*.rb")).each { |f| require_relative f }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JmeterPerf
4
- VERSION = "1.0.10"
4
+ VERSION = "1.1.1"
5
5
  end
@@ -57,28 +57,28 @@
57
57
  </style>
58
58
  </head>
59
59
  <body>
60
- <% if @comparator.name %>
61
- <h2>Comparison Report for <span style="text-decoration: underline;"><%= @comparator.name %></span></h2>
60
+ <% if @name %>
61
+ <h2>Comparison Report for <span style="text-decoration: underline;"><%= @name %></span></h2>
62
62
  <% end %>
63
63
  <div class="summary-box" id="summary-box">
64
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
65
+ <b>Cohen's D:</b> <%= @cohens_d %><br>
66
+ <b>T Statistic:</b> <%= @t_statistic %><br>
67
+ <b>Summary:</b> <%= @human_rating %> in performance
68
68
  </div>
69
69
  <table>
70
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| %>
71
+ <% JmeterPerf::Report::Comparator::COMPARISON_REPORT_HEADER.each do |header| %>
72
72
  <td class="tooltip"><%= header %>
73
73
  <span class="tooltiptext"><%= "Description for #{header}" %></span>
74
74
  </td>
75
75
  <% end %>
76
76
  </tr>
77
- <% @reports.each_with_index do |report, index| %>
77
+ <% [@base_report, @test_report].each_with_index do |report, index| %>
78
78
  <tr>
79
79
  <td><%= index == 0 ? "Base Metric" : "Test Metric" %></td>
80
80
  <td><%= report.total_requests %></td>
81
- <td><%= report.total_elapsed_time %></td>
81
+ <td><%= report.total_run_time %></td>
82
82
  <td><%= sprintf('%.2f', report.rpm) %></td>
83
83
  <td class="<%= 'bad' if report.total_errors > 0 %>"><%= report.total_errors %></td>
84
84
  <td class="<%= 'bad' if report.error_percentage > 0.0 %>"><%= sprintf('%.2f', report.error_percentage) %></td>
@@ -96,7 +96,7 @@
96
96
  import ColorScale from "https://cdn.skypack.dev/color-scales";
97
97
  document.addEventListener('DOMContentLoaded', function () {
98
98
  const summaryBox = document.getElementById('summary-box');
99
- const cohenValue = <%= @comparator.cohens_d %>;
99
+ const cohenValue = <%= @cohens_d %>;
100
100
  let color;
101
101
 
102
102
  if (cohenValue === 0) {
data/lib/jmeter_perf.rb CHANGED
@@ -61,7 +61,7 @@ module JmeterPerf
61
61
  @root = Nokogiri::XML(JmeterPerf::Helpers::String.strip_heredoc(
62
62
  <<-EOF
63
63
  <?xml version="1.0" encoding="UTF-8"?>
64
- <jmeterTestPlan version="1.2" properties="3.1" jmeter="3.1" ruby-jmeter="3.0">
64
+ <jmeterTestPlan version="1.2" properties="3.1" jmeter="3.1" jmeterperf="#{JmeterPerf::VERSION}">
65
65
  <hashTree>
66
66
  </hashTree>
67
67
  </jmeterTestPlan>
@@ -74,17 +74,17 @@ module JmeterPerf
74
74
 
75
75
  # Saves the test plan as a JMX file.
76
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")
77
+ # @param out_jmx [String] The path for the output JMX file (default: `"jmeter_perf.jmx"`).
78
+ def jmx(out_jmx: "jmeter_perf.jmx")
79
79
  File.write(out_jmx, doc.to_xml(indent: 2))
80
80
  logger.info "JMX saved to: #{out_jmx}"
81
81
  end
82
82
 
83
83
  # Runs the test plan with the specified configuration.
84
84
  #
85
- # @param name [String] The name of the test run (default: `"ruby-jmeter"`).
85
+ # @param name [String] The name of the test run (default: `"jmeter_perf"`).
86
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"`).
87
+ # @param out_jmx [String] The filename for the output JMX file (default: `"jmeter_perf.jmx"`).
88
88
  # @param out_jtl [String] The filename for the output JTL file (default: `"jmeter.jtl"`).
89
89
  # @param out_jmeter_log [String] The filename for the JMeter log file (default: `"jmeter.log"`).
90
90
  # @param out_cmd_log [String] The filename for the command log file (default: `"jmeter-cmd.log"`).
@@ -92,14 +92,15 @@ module JmeterPerf
92
92
  # @return [JmeterPerf::Report::Summary] The summary report of the test run.
93
93
  # @raise [RuntimeError] If the test execution fails.
94
94
  def run(
95
- name: "ruby-jmeter",
95
+ name: "jmeter_perf",
96
96
  jmeter_path: "jmeter",
97
- out_jmx: "ruby-jmeter.jmx",
97
+ out_jmx: "jmeter_perf.jmx",
98
98
  out_jtl: "jmeter.jtl",
99
99
  out_jmeter_log: "jmeter.log",
100
100
  out_cmd_log: "jmeter-cmd.log",
101
101
  jtl_read_timeout: 3
102
102
  )
103
+ summary = nil
103
104
  jmx(out_jmx:)
104
105
  logger.warn "Executing #{out_jmx} test plan locally ..."
105
106
 
@@ -108,15 +109,14 @@ module JmeterPerf
108
109
  CMD
109
110
 
110
111
  summary = JmeterPerf::Report::Summary.new(file_path: out_jtl, name:, jtl_read_timeout:)
111
- jtl_process_thread = summary.stream_jtl_async
112
+ summary.stream_jtl_async
112
113
 
113
114
  File.open(out_cmd_log, "w") do |f|
114
115
  pid = Process.spawn(cmd, out: f, err: [:child, :out])
115
116
  Process.waitpid(pid)
116
117
  end
117
118
 
118
- summary.finish! # Notify jtl collection that JTL cmd finished
119
- jtl_process_thread.join # Join main thread and wait for it to finish
119
+ summary.finish! # Notify jtl collection that JTL cmd finished and waits
120
120
 
121
121
  unless $?.exitstatus.zero?
122
122
  logger.error("Failed to run #{cmd}. See #{out_cmd_log} and #{out_jmeter_log} for details.")
@@ -126,9 +126,8 @@ module JmeterPerf
126
126
  summary.summarize_data!
127
127
  logger.info "[Test Plan Execution Completed Successfully] JTL saved to: #{out_jtl}\n"
128
128
  summary
129
- rescue
130
- summary.finish!
131
- raise
129
+ ensure
130
+ summary&.finish!
132
131
  end
133
132
 
134
133
  private
data/sig/jmeter_perf.rbs CHANGED
@@ -1,195 +1,68 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JmeterPerf
2
- def self.test: (Hash[Symbol, untyped] params = {}) { () -> void } -> ExtendedDSL
4
+ def self.test: (params: Hash[Symbol, untyped] ?, &block: untyped) -> ExtendedDSL
3
5
 
4
6
  class ExtendedDSL < DSL
5
- include JmeterPerf::Helper::Parser
6
- attr_accessor root: Nokogiri::XML::Document
7
+ include JmeterPerf::Helpers::Parser
7
8
 
8
- def initialize: (Hash[Symbol, untyped]) -> void
9
+ attr_accessor root: ::Nokogiri::XML::Document
9
10
 
10
- def jmx: (out_jmx: String) -> void
11
+ def initialize: (params: Hash[Symbol, untyped] ?) -> void
12
+ def jmx: (out_jmx: String = "jmeter_perf.jmx") -> void
11
13
  def run: (
12
- name: String,
13
- jmeter_path: String?,
14
- out_jmx: String,
15
- out_jtl: String,
16
- out_jmeter_log: String,
17
- out_cmd_log: String
18
- ) -> Report::Summary
14
+ name: String = "jmeter_perf",
15
+ jmeter_path: String = "jmeter",
16
+ out_jmx: String = "jmeter_perf.jmx",
17
+ out_jtl: String = "jmeter.jtl",
18
+ out_jmeter_log: String = "jmeter.log",
19
+ out_cmd_log: String = "jmeter-cmd.log",
20
+ jtl_read_timeout: Integer = 3
21
+ ) -> JmeterPerf::Report::Summary
19
22
  end
20
23
 
21
24
  module Report
22
- class Summary
23
- attr_reader name: String
24
- attr_reader avg: Float
25
- attr_reader error_percentage: Float
26
- attr_reader max: Integer
27
- attr_reader min: Integer
28
- attr_reader p10: Float
29
- attr_reader p50: Float
30
- attr_reader p95: Float
31
- attr_reader requests_per_minute: Float
32
- attr_reader response_codes: Hash[String, Integer]
33
- attr_reader standard_deviation: Float
34
- attr_reader total_bytes: Integer
35
- attr_reader total_elapsed_time: Integer
36
- attr_reader total_errors: Integer
37
- attr_reader total_latency: Integer
38
- attr_reader total_requests: Integer
39
- attr_reader total_sent_bytes: Integer
40
-
41
- alias rpm: requests_per_minute
42
- alias std: standard_deviation
43
- alias median: p50
44
-
45
- def initialize: (String | Pathname, String?) -> void
46
- def finish!: () -> void
47
- def stream_jtl_async: () -> Thread
48
- def summarize_data!: () -> void
49
- end
50
-
51
25
  class Comparator
52
26
  attr_reader cohens_d: Float
53
27
  attr_reader t_statistic: Float
54
28
  attr_reader human_rating: String
55
29
  attr_reader name: String
56
30
 
57
- def initialize: (Summary, Summary, String?) -> void
58
- def pass?: (Float?, Symbol, Symbol) -> bool
59
- def generate_reports: (output_dir: String, output_format: Symbol) -> void
31
+ def initialize: (base_report: Summary, test_report: Summary, name: String ?) -> void
32
+ def pass?: (cohens_d_limit: Float ?, effect_size: Symbol = :vsmall) -> bool
33
+ def generate_reports: (output_dir: String = ".", output_format: Symbol = :all) -> void
34
+ def to_s: () -> String
35
+ def print_comparison: () -> void
60
36
  end
61
37
  end
62
38
 
63
- class DSLGenerator
64
- def initialize: (lib_dir: String, gem_dir: String, dsl_dir: String, idl_xml_path: String) -> void
65
- def generate: () -> void
66
- end
67
-
68
- class DSL
69
- def method_missing: (Symbol, *untyped, **untyped) { () -> void } -> void
70
- def respond_to_missing?: (Symbol, bool) -> bool
71
-
72
- # DSL Methods
73
- ## AUTOGENERATED - DSL methods RBS
74
-
75
- def ajp13_sampler: (Hash[Symbol, untyped], &block) -> void
76
- def access_log_sampler: (Hash[Symbol, untyped], &block) -> void
77
- def aggregate_graph: (Hash[Symbol, untyped], &block) -> void
78
- def aggregate_report: (Hash[Symbol, untyped], &block) -> void
79
- def assertion_results: (Hash[Symbol, untyped], &block) -> void
80
- def bsf_assertion: (Hash[Symbol, untyped], &block) -> void
81
- def bsf_listener: (Hash[Symbol, untyped], &block) -> void
82
- def bsf_postprocessor: (Hash[Symbol, untyped], &block) -> void
83
- def bsf_preprocessor: (Hash[Symbol, untyped], &block) -> void
84
- def bsf_sampler: (Hash[Symbol, untyped], &block) -> void
85
- def bsf_timer: (Hash[Symbol, untyped], &block) -> void
86
- def bean_shell_assertion: (Hash[Symbol, untyped], &block) -> void
87
- def bean_shell_listener: (Hash[Symbol, untyped], &block) -> void
88
- def bean_shell_postprocessor: (Hash[Symbol, untyped], &block) -> void
89
- def bean_shell_preprocessor: (Hash[Symbol, untyped], &block) -> void
90
- def bean_shell_sampler: (Hash[Symbol, untyped], &block) -> void
91
- def bean_shell_timer: (Hash[Symbol, untyped], &block) -> void
92
- def css_jquery_extractor: (Hash[Symbol, untyped], &block) -> void
93
- def csv_data_set_config: (Hash[Symbol, untyped], &block) -> void
94
- def compare_assertion: (Hash[Symbol, untyped], &block) -> void
95
- def comparison_assertion_visualizer: (Hash[Symbol, untyped], &block) -> void
96
- def constant_throughput_timer: (Hash[Symbol, untyped], &block) -> void
97
- def constant_timer: (Hash[Symbol, untyped], &block) -> void
98
- def counter: (Hash[Symbol, untyped], &block) -> void
99
- def debug_postprocessor: (Hash[Symbol, untyped], &block) -> void
100
- def debug_sampler: (Hash[Symbol, untyped], &block) -> void
101
- def distribution_graphalpha: (Hash[Symbol, untyped], &block) -> void
102
- def duration_assertion: (Hash[Symbol, untyped], &block) -> void
103
- def ftp_request: (Hash[Symbol, untyped], &block) -> void
104
- def ftp_request_defaults: (Hash[Symbol, untyped], &block) -> void
105
- def for_each_controller: (Hash[Symbol, untyped], &block) -> void
106
- def gaussian_random_timer: (Hash[Symbol, untyped], &block) -> void
107
- def generate_summary_results: (Hash[Symbol, untyped], &block) -> void
108
- def graph_results: (Hash[Symbol, untyped], &block) -> void
109
- def html_assertion: (Hash[Symbol, untyped], &block) -> void
110
- def html_link_parser: (Hash[Symbol, untyped], &block) -> void
111
- def html_parameter_mask: (Hash[Symbol, untyped], &block) -> void
112
- def http_authorization_manager: (Hash[Symbol, untyped], &block) -> void
113
- def http_cache_manager: (Hash[Symbol, untyped], &block) -> void
114
- def http_cookie_manager: (Hash[Symbol, untyped], &block) -> void
115
- def http_header_manager: (Hash[Symbol, untyped], &block) -> void
116
- def http_request: (Hash[Symbol, untyped], &block) -> void
117
- def http_request_defaults: (Hash[Symbol, untyped], &block) -> void
118
- def http_url_rewriting_modifier: (Hash[Symbol, untyped], &block) -> void
119
- def if_controller: (Hash[Symbol, untyped], &block) -> void
120
- def include_controller: (Hash[Symbol, untyped], &block) -> void
121
- def jdbc_connection_configuration: (Hash[Symbol, untyped], &block) -> void
122
- def jdbc_postprocessor: (Hash[Symbol, untyped], &block) -> void
123
- def jdbc_preprocessor: (Hash[Symbol, untyped], &block) -> void
124
- def jdbc_request: (Hash[Symbol, untyped], &block) -> void
125
- def jms_pointto_point: (Hash[Symbol, untyped], &block) -> void
126
- def jms_publisher: (Hash[Symbol, untyped], &block) -> void
127
- def jms_subscriber: (Hash[Symbol, untyped], &block) -> void
128
- def json_path_postprocessor: (Hash[Symbol, untyped], &block) -> void
129
- def jsr223_assertion: (Hash[Symbol, untyped], &block) -> void
130
- def jsr223_listener: (Hash[Symbol, untyped], &block) -> void
131
- def jsr223_postprocessor: (Hash[Symbol, untyped], &block) -> void
132
- def jsr223_preprocessor: (Hash[Symbol, untyped], &block) -> void
133
- def jsr223_sampler: (Hash[Symbol, untyped], &block) -> void
134
- def jsr223_timer: (Hash[Symbol, untyped], &block) -> void
135
- def j_unit_request: (Hash[Symbol, untyped], &block) -> void
136
- def java_request: (Hash[Symbol, untyped], &block) -> void
137
- def java_request_defaults: (Hash[Symbol, untyped], &block) -> void
138
- def keystore_configuration: (Hash[Symbol, untyped], &block) -> void
139
- def ldap_extended_request: (Hash[Symbol, untyped], &block) -> void
140
- def ldap_extended_request_defaults: (Hash[Symbol, untyped], &block) -> void
141
- def ldap_request: (Hash[Symbol, untyped], &block) -> void
142
- def ldap_request_defaults: (Hash[Symbol, untyped], &block) -> void
143
- def login_config_element: (Hash[Symbol, untyped], &block) -> void
144
- def loop_controller: (Hash[Symbol, untyped], &block) -> void
145
- def md5_hex_assertion: (Hash[Symbol, untyped], &block) -> void
146
- def mail_reader_sampler: (Hash[Symbol, untyped], &block) -> void
147
- def mailer_visualizer: (Hash[Symbol, untyped], &block) -> void
148
- def module_controller: (Hash[Symbol, untyped], &block) -> void
149
- def monitor_results: (Hash[Symbol, untyped], &block) -> void
150
- def os_process_sampler: (Hash[Symbol, untyped], &block) -> void
151
- def once_only_controller: (Hash[Symbol, untyped], &block) -> void
152
- def poisson_random_timer: (Hash[Symbol, untyped], &block) -> void
153
- def random_controller: (Hash[Symbol, untyped], &block) -> void
154
- def random_order_controller: (Hash[Symbol, untyped], &block) -> void
155
- def random_variable: (Hash[Symbol, untyped], &block) -> void
156
- def recording_controller: (Hash[Symbol, untyped], &block) -> void
157
- def reg_ex_user_parameters: (Hash[Symbol, untyped], &block) -> void
158
- def regular_expression_extractor: (Hash[Symbol, untyped], &block) -> void
159
- def response_assertion: (Hash[Symbol, untyped], &block) -> void
160
- def response_time_graph: (Hash[Symbol, untyped], &block) -> void
161
- def result_status_action_handler: (Hash[Symbol, untyped], &block) -> void
162
- def runtime_controller: (Hash[Symbol, untyped], &block) -> void
163
- def smime_assertion: (Hash[Symbol, untyped], &block) -> void
164
- def smtp_sampler: (Hash[Symbol, untyped], &block) -> void
165
- def soap_xml_rpc_request: (Hash[Symbol, untyped], &block) -> void
166
- def save_responses_to_a_file: (Hash[Symbol, untyped], &block) -> void
167
- def simple_config_element: (Hash[Symbol, untyped], &block) -> void
168
- def simple_controller: (Hash[Symbol, untyped], &block) -> void
169
- def simple_data_writer: (Hash[Symbol, untyped], &block) -> void
170
- def spline_visualizer: (Hash[Symbol, untyped], &block) -> void
171
- def summary_report: (Hash[Symbol, untyped], &block) -> void
172
- def switch_controller: (Hash[Symbol, untyped], &block) -> void
173
- def synchronizing_timer: (Hash[Symbol, untyped], &block) -> void
174
- def tcp_sampler: (Hash[Symbol, untyped], &block) -> void
175
- def tcp_sampler_config: (Hash[Symbol, untyped], &block) -> void
176
- def test_action: (Hash[Symbol, untyped], &block) -> void
177
- def test_fragment: (Hash[Symbol, untyped], &block) -> void
178
- def test_plan: (Hash[Symbol, untyped], &block) -> void
179
- def thread_group: (Hash[Symbol, untyped], &block) -> void
180
- def throughput_controller: (Hash[Symbol, untyped], &block) -> void
181
- def transaction_controller: (Hash[Symbol, untyped], &block) -> void
182
- def uniform_random_timer: (Hash[Symbol, untyped], &block) -> void
183
- def user_defined_variables: (Hash[Symbol, untyped], &block) -> void
184
- def user_parameters: (Hash[Symbol, untyped], &block) -> void
185
- def view_results_in_table: (Hash[Symbol, untyped], &block) -> void
186
- def view_results_tree: (Hash[Symbol, untyped], &block) -> void
187
- def while_controller: (Hash[Symbol, untyped], &block) -> void
188
- def xml_assertion: (Hash[Symbol, untyped], &block) -> void
189
- def xml_schema_assertion: (Hash[Symbol, untyped], &block) -> void
190
- def x_path_assertion: (Hash[Symbol, untyped], &block) -> void
191
- def x_path_extractor: (Hash[Symbol, untyped], &block) -> void
39
+ module Report
40
+ class Summary
41
+ attr_accessor avg: Float
42
+ attr_accessor error_percentage: Float
43
+ attr_accessor max: Integer
44
+ attr_accessor min: Integer
45
+ attr_accessor p10: Float
46
+ attr_accessor p50: Float
47
+ attr_accessor p95: Float
48
+ attr_accessor requests_per_minute: Float
49
+ attr_accessor response_codes: Hash[String, Integer]
50
+ attr_accessor standard_deviation: Float
51
+ attr_accessor total_bytes: Integer
52
+ attr_accessor total_errors: Integer
53
+ attr_accessor total_latency: Integer
54
+ attr_accessor total_requests: Integer
55
+ attr_accessor total_sent_bytes: Integer
56
+ attr_accessor csv_error_lines: Array[Integer]
57
+ attr_accessor total_run_time: Integer
58
+ attr_accessor name: String
192
59
 
193
- ## AUTOGENERATED - DSL methods RBS
60
+ def self.read: (csv_path: String) -> Summary
61
+ def initialize: (file_path: String, name: String ? = nil, jtl_read_timeout: Integer = 3) -> void
62
+ def finish!: () -> void
63
+ def write_csv: (output_file: String) -> void
64
+ def stream_jtl_async: () -> Thread
65
+ def summarize_data!: () -> void
66
+ end
194
67
  end
195
68
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jmeter_perf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.10
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Luis Urena
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-01 00:00:00.000000000 Z
11
+ date: 2024-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri