lib/jmeter_perf/report/summary.rb in jmeter_perf-1.0.6 vs lib/jmeter_perf/report/summary.rb in jmeter_perf-1.0.7
- old
+ new
@@ -1,58 +1,60 @@
require "csv"
require "timeout"
require_relative "../helpers/running_statistics"
-
module JmeterPerf
module Report
# Summary provides a statistical analysis of performance test results by processing
# JMeter JTL files. It calculates metrics such as average response time, error percentage,
# min/max response times, and percentiles, helping to understand the distribution of
# response times across requests.
# @note This class uses a TDigest data structure to keep statistics "close enough". Accuracy is not guaranteed.
- class Summary
- # @return [String] the name of the summary, usually derived from the file path
- attr_reader :name
- # @return [Float] the average response time
- attr_reader :avg
- # @return [Float] the error percentage across all requests
- attr_reader :error_percentage
- # @return [Integer] the maximum response time encountered
- attr_reader :max
- # @return [Integer] the minimum response time encountered
- attr_reader :min
- # @return [Float] the 10th percentile of response times
- attr_reader :p10
- # @return [Float] the median (50th percentile) of response times
- attr_reader :p50
- # @return [Float] the 95th percentile of response times
- attr_reader :p95
- # @return [Float] the requests per minute rate
- attr_reader :requests_per_minute
- # @return [Hash<String, Integer>] a hash of response codes with their respective counts
- attr_reader :response_codes
- # @return [Float] the standard deviation of response times
- attr_reader :standard_deviation
- # @return [Integer] the total number of bytes received
- attr_reader :total_bytes
- # @return [Integer] the total elapsed time in milliseconds
- attr_reader :total_elapsed_time
- # @return [Integer] the total number of errors encountered
- attr_reader :total_errors
- # @return [Integer] the total latency time across all requests
- attr_reader :total_latency
- # @return [Integer] the total number of requests processed
- attr_reader :total_requests
- # @return [Integer] the total number of bytes sent
- attr_reader :total_sent_bytes
- # @return [Array<Integer>] the line numbers of where CSV errors were encountered
- attr_reader :csv_error_lines
+ #
- alias_method :rpm, :requests_per_minute
- alias_method :std, :standard_deviation
- alias_method :median, :p50
-
+ # @!attribute [rw] avg
+ # @return [Float] the average response time
+ # @!attribute [rw] error_percentage
+ # @return [Float] the error percentage across all requests
+ # @!attribute [rw] max
+ # @return [Integer] the maximum response time encountered
+ # @!attribute [rw] min
+ # @return [Integer] the minimum response time encountered
+ # @!attribute [rw] p10
+ # @return [Float] the 10th percentile of response times
+ # @!attribute [rw] p50
+ # @return [Float] the median (50th percentile) of response times
+ # @!attribute [rw] p95
+ # @return [Float] the 95th percentile of response times
+ # @!attribute [rw] requests_per_minute
+ # @return [Float] the requests per minute rate
+ # @!attribute [rw] response_codes
+ # @return [Hash<String, Integer>] a hash of response codes with their respective counts
+ # @!attribute [rw] standard_deviation
+ # @return [Float] the standard deviation of response times
+ # @!attribute [rw] total_bytes
+ # @return [Integer] the total number of bytes received
+ # @!attribute [rw] total_elapsed_time
+ # @return [Integer] the total elapsed time in milliseconds
+ # @!attribute [rw] total_errors
+ # @return [Integer] the total number of errors encountered
+ # @!attribute [rw] total_latency
+ # @return [Integer] the total latency time across all requests
+ # @!attribute [rw] total_requests
+ # @return [Integer] the total number of requests processed
+ # @!attribute [rw] total_sent_bytes
+ # @return [Integer] the total number of bytes sent
+ # @!attribute [rw] csv_error_lines
+ # @return [Array<Integer>] the line numbers of where CSV errors were encountered
+ # @!attribute [rw] total_run_time
+ # @return [Integer] the total run time in seconds
+ # @!attribute [rw] name
+ # @return [String] the name of the summary, derived from the file path if not provided
+ # @!attribute [rw] response_codes
+ # @return [Hash<String, Integer>] a hash of response codes with their respective counts
+ # @!attribute [rw] csv_error_lines
+ # @return [Array<Integer>] the line numbers of where CSV errors were encountered
+ class Summary
# JTL file headers used for parsing CSV rows
JTL_HEADER = %i[
timeStamp
elapsed
label
@@ -72,32 +74,39 @@
Connect
]
# @return [Hash<String, Symbol>] a mapping of CSV headers to their corresponding attribute symbols.
CSV_HEADER_MAPPINGS = {
+ "Name" => :name,
"Average Response Time" => :avg,
"Error Percentage" => :error_percentage,
"Max Response Time" => :max,
"Min Response Time" => :min,
"10th Percentile" => :p10,
"Median (50th Percentile)" => :p50,
"95th Percentile" => :p95,
"Requests Per Minute" => :requests_per_minute,
"Standard Deviation" => :standard_deviation,
+ "Total Run Time" => :total_run_time,
"Total Bytes" => :total_bytes,
"Total Elapsed Time" => :total_elapsed_time,
"Total Errors" => :total_errors,
"Total Latency" => :total_latency,
"Total Requests" => :total_requests,
"Total Sent Bytes" => :total_sent_bytes
- # "CSV Errors" => :csv_error_lines
# "Response Code 200" => :response_codes["200"],
# "Response Code 500" => :response_codes["500"] etc.
}
- attr_writer(*CSV_HEADER_MAPPINGS.values)
- attr_writer :response_codes, :csv_error_lines
+ attr_accessor(*CSV_HEADER_MAPPINGS.values)
+ # Response codes have multiple keys, so we need to handle them separately
+ attr_accessor :response_codes
+ # CSV Error Lines are an array of integers that get delimited by ":" when written to the CSV
+ attr_accessor :csv_error_lines
+ alias_method :rpm, :requests_per_minute
+ alias_method :std, :standard_deviation
+ alias_method :median, :p50
# Reads a generated CSV report and sets all appropriate attributes.
#
# @param csv_path [String] the file path of the CSV report to read
# @return [Summary] a new Summary instance with the parsed data
@@ -105,17 +114,19 @@
summary = new(file_path: csv_path)
CSV.foreach(csv_path, headers: true) do |row|
metric = row["Metric"]
value = row["Value"]
- if CSV_HEADER_MAPPINGS.key?(metric)
- summary.public_send(:"#{CSV_HEADER_MAPPINGS[metric]}=", value.include?(".") ? value.to_f : value.to_i)
+ if metric == "Name"
+ summary.name = value
elsif metric.start_with?("Response Code")
code = metric.split.last
summary.response_codes[code] = value.to_i
elsif metric == "CSV Errors"
summary.csv_error_lines = value.split(":").map(&:to_i)
+ elsif CSV_HEADER_MAPPINGS.key?(metric)
+ summary.public_send(:"#{CSV_HEADER_MAPPINGS[metric]}=", value.include?(".") ? value.to_f : value.to_i)
end
end
summary
end
@@ -140,10 +151,13 @@
@total_requests = 0
@total_sent_bytes = 0
@csv_error_lines = []
@file_path = file_path
+
+ @start_time = nil
+ @end_time = nil
end
# Marks the summary as finished, allowing any pending asynchronous operations to complete.
#
# @return [void]
@@ -201,11 +215,12 @@
# @return [void]
def summarize_data!
@p10, @p50, @p95 = @running_statistics_helper.get_percentiles(0.1, 0.5, 0.95)
@error_percentage = (@total_errors.to_f / @total_requests) * 100
@avg = @running_statistics_helper.avg
- @requests_per_minute = @total_elapsed_time.zero? ? 0 : (@total_requests / (@total_elapsed_time / 1000)) * 60
+ @total_run_time = ((@end_time - @start_time) / 1000).to_f # Convert milliseconds to seconds
+ @requests_per_minute = @total_run_time.zero? ? 0 : (@total_requests / @total_run_time) * 60.0
@standard_deviation = @running_statistics_helper.std
end
private
@@ -227,10 +242,16 @@
def parse_csv_row(line)
CSV.parse(line, headers: JTL_HEADER, liberal_parsing: true).each do |row|
line_item = row.to_hash
elapsed = line_item.fetch(:elapsed).to_i
+ timestamp = line_item.fetch(:timeStamp).to_i
+ # Update start and end times
+ @start_time = timestamp if @start_time.nil? || timestamp < @start_time
+ @end_time = timestamp + elapsed if @end_time.nil? || (timestamp + elapsed) > @end_time
+
+ # Continue with processing the row as before...
@running_statistics_helper.add_number(elapsed)
@total_requests += 1
@total_elapsed_time += elapsed
@response_codes[line_item.fetch(:responseCode)] += 1
@total_errors += (line_item.fetch(:success) == "true") ? 0 : 1