jmeter_perf 1.0.10 → 1.1.1

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