the_mechanic_2 0.1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +254 -0
- data/Rakefile +5 -0
- data/app/assets/javascripts/the_mechanic_2/application.js +426 -0
- data/app/assets/stylesheets/the_mechanic_2/application.css +666 -0
- data/app/controllers/the_mechanic_2/application_controller.rb +6 -0
- data/app/controllers/the_mechanic_2/benchmarks_controller.rb +174 -0
- data/app/helpers/the_mechanic_2/application_helper.rb +4 -0
- data/app/jobs/the_mechanic_2/application_job.rb +4 -0
- data/app/mailers/the_mechanic_2/application_mailer.rb +6 -0
- data/app/models/the_mechanic_2/application_record.rb +5 -0
- data/app/models/the_mechanic_2/benchmark_request.rb +79 -0
- data/app/models/the_mechanic_2/benchmark_result.rb +136 -0
- data/app/services/the_mechanic_2/benchmark_service.rb +115 -0
- data/app/services/the_mechanic_2/rails_runner_service.rb +235 -0
- data/app/services/the_mechanic_2/security_service.rb +128 -0
- data/app/views/layouts/the_mechanic_2/application.html.erb +17 -0
- data/app/views/the_mechanic_2/benchmarks/index.html.erb +266 -0
- data/config/routes.rb +7 -0
- data/lib/tasks/the_mechanic_tasks.rake +4 -0
- data/lib/the_mechanic_2/configuration.rb +45 -0
- data/lib/the_mechanic_2/engine.rb +18 -0
- data/lib/the_mechanic_2/version.rb +3 -0
- data/lib/the_mechanic_2.rb +7 -0
- metadata +157 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TheMechanic2
|
|
4
|
+
# Main controller for benchmark operations
|
|
5
|
+
# Handles UI rendering and API endpoints
|
|
6
|
+
class BenchmarksController < ApplicationController
|
|
7
|
+
before_action :check_authentication, if: -> { TheMechanic2.configuration.enable_authentication }
|
|
8
|
+
|
|
9
|
+
# GET /ask_the_mechanic
|
|
10
|
+
# Renders the main benchmarking UI
|
|
11
|
+
def index
|
|
12
|
+
render template: 'the_mechanic_2/benchmarks/index'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# POST /ask_the_mechanic_2/validate
|
|
16
|
+
# Validates code without executing
|
|
17
|
+
def validate
|
|
18
|
+
code_a = params[:code_a]
|
|
19
|
+
code_b = params[:code_b]
|
|
20
|
+
|
|
21
|
+
errors = []
|
|
22
|
+
|
|
23
|
+
# Validate code_a
|
|
24
|
+
if code_a.present?
|
|
25
|
+
validation_a = SecurityService.validate(code_a)
|
|
26
|
+
errors.concat(validation_a[:errors].map { |e| "Code A: #{e}" }) unless validation_a[:valid]
|
|
27
|
+
else
|
|
28
|
+
errors << 'Code A: Code is required'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Validate code_b
|
|
32
|
+
if code_b.present?
|
|
33
|
+
validation_b = SecurityService.validate(code_b)
|
|
34
|
+
errors.concat(validation_b[:errors].map { |e| "Code B: #{e}" }) unless validation_b[:valid]
|
|
35
|
+
else
|
|
36
|
+
errors << 'Code B: Code is required'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Validate shared_setup if provided
|
|
40
|
+
if params[:shared_setup].present?
|
|
41
|
+
validation_setup = SecurityService.validate(params[:shared_setup])
|
|
42
|
+
errors.concat(validation_setup[:errors].map { |e| "Shared Setup: #{e}" }) unless validation_setup[:valid]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if errors.empty?
|
|
46
|
+
render json: { valid: true, message: 'All code is valid' }
|
|
47
|
+
else
|
|
48
|
+
render json: { valid: false, errors: errors }, status: :unprocessable_entity
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# POST /ask_the_mechanic_2/run
|
|
53
|
+
# Executes benchmark comparison
|
|
54
|
+
def run
|
|
55
|
+
# Validate request
|
|
56
|
+
request = BenchmarkRequest.new(
|
|
57
|
+
shared_setup: params[:shared_setup],
|
|
58
|
+
code_a: params[:code_a],
|
|
59
|
+
code_b: params[:code_b],
|
|
60
|
+
timeout: params[:timeout]&.to_i
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
unless request.valid?
|
|
64
|
+
render json: { error: 'Invalid request', errors: request.all_errors }, status: :unprocessable_entity
|
|
65
|
+
return
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Validate code security
|
|
69
|
+
validation_result = validate_code_security(request)
|
|
70
|
+
unless validation_result[:valid]
|
|
71
|
+
render json: { error: 'Security validation failed', errors: validation_result[:errors] }, status: :unprocessable_entity
|
|
72
|
+
return
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Execute benchmark
|
|
76
|
+
begin
|
|
77
|
+
service = BenchmarkService.new
|
|
78
|
+
results = service.run(
|
|
79
|
+
shared_setup: request.shared_setup,
|
|
80
|
+
code_a: request.code_a,
|
|
81
|
+
code_b: request.code_b,
|
|
82
|
+
timeout: request.timeout
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
render json: results
|
|
86
|
+
rescue RailsRunnerService::BenchmarkTimeout => e
|
|
87
|
+
render json: { error: 'Benchmark timeout', message: e.message }, status: :request_timeout
|
|
88
|
+
rescue RailsRunnerService::BenchmarkError => e
|
|
89
|
+
render json: { error: 'Benchmark execution failed', message: e.message }, status: :internal_server_error
|
|
90
|
+
rescue StandardError => e
|
|
91
|
+
render json: { error: 'Unexpected error', message: e.message }, status: :internal_server_error
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# POST /ask_the_mechanic_2/export
|
|
96
|
+
# Exports results in specified format
|
|
97
|
+
def export
|
|
98
|
+
results_data = params[:results]
|
|
99
|
+
format = params[:format] || 'json'
|
|
100
|
+
|
|
101
|
+
unless results_data
|
|
102
|
+
render json: { error: 'Results data is required' }, status: :unprocessable_entity
|
|
103
|
+
return
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
begin
|
|
107
|
+
result = BenchmarkResult.new(results_data.permit!.to_h.symbolize_keys)
|
|
108
|
+
|
|
109
|
+
case format
|
|
110
|
+
when 'json'
|
|
111
|
+
render json: result.to_json, content_type: 'application/json'
|
|
112
|
+
when 'markdown'
|
|
113
|
+
render plain: result.to_markdown, content_type: 'text/markdown'
|
|
114
|
+
else
|
|
115
|
+
render json: { error: 'Invalid format. Use json or markdown' }, status: :unprocessable_entity
|
|
116
|
+
end
|
|
117
|
+
rescue StandardError => e
|
|
118
|
+
render json: { error: 'Export failed', message: e.message }, status: :internal_server_error
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# Checks authentication using configured callback
|
|
125
|
+
def check_authentication
|
|
126
|
+
callback = TheMechanic2.configuration.authentication_callback
|
|
127
|
+
return unless callback
|
|
128
|
+
|
|
129
|
+
unless callback.call(self)
|
|
130
|
+
render json: { error: 'Unauthorized' }, status: :unauthorized
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Validates code security for all code snippets
|
|
135
|
+
def validate_code_security(request)
|
|
136
|
+
errors = []
|
|
137
|
+
|
|
138
|
+
# Validate shared_setup if present
|
|
139
|
+
if request.shared_setup.present?
|
|
140
|
+
validation = SecurityService.validate(request.shared_setup)
|
|
141
|
+
errors.concat(validation[:errors].map { |e| "Shared Setup: #{e}" }) unless validation[:valid]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Validate code_a
|
|
145
|
+
validation_a = SecurityService.validate(request.code_a)
|
|
146
|
+
errors.concat(validation_a[:errors].map { |e| "Code A: #{e}" }) unless validation_a[:valid]
|
|
147
|
+
|
|
148
|
+
# Validate code_b
|
|
149
|
+
validation_b = SecurityService.validate(request.code_b)
|
|
150
|
+
errors.concat(validation_b[:errors].map { |e| "Code B: #{e}" }) unless validation_b[:valid]
|
|
151
|
+
|
|
152
|
+
{ valid: errors.empty?, errors: errors }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Helper methods for asset inlining (to be implemented later)
|
|
156
|
+
helper_method :inline_css, :inline_javascript
|
|
157
|
+
|
|
158
|
+
def inline_css
|
|
159
|
+
@inline_css ||= read_asset_file('stylesheets/the_mechanic_2/application.css')
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def inline_javascript
|
|
163
|
+
@inline_javascript ||= read_asset_file('javascripts/the_mechanic_2/application.js')
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def read_asset_file(path)
|
|
167
|
+
file_path = TheMechanic2::Engine.root.join('app', 'assets', path)
|
|
168
|
+
File.exist?(file_path) ? File.read(file_path) : ''
|
|
169
|
+
rescue StandardError => e
|
|
170
|
+
Rails.logger.error("Failed to read asset file #{path}: #{e.message}")
|
|
171
|
+
''
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TheMechanic2
|
|
4
|
+
# Model for validating benchmark request parameters
|
|
5
|
+
# Ensures all required fields are present and valid
|
|
6
|
+
class BenchmarkRequest
|
|
7
|
+
attr_reader :shared_setup, :code_a, :code_b, :timeout, :errors
|
|
8
|
+
|
|
9
|
+
# Minimum and maximum allowed timeout values
|
|
10
|
+
MIN_TIMEOUT = 1
|
|
11
|
+
MAX_TIMEOUT = 300
|
|
12
|
+
|
|
13
|
+
def initialize(params = {})
|
|
14
|
+
@shared_setup = params[:shared_setup]
|
|
15
|
+
@code_a = params[:code_a]
|
|
16
|
+
@code_b = params[:code_b]
|
|
17
|
+
@timeout = params[:timeout] || TheMechanic2.configuration.timeout
|
|
18
|
+
@errors = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Validates the request parameters
|
|
22
|
+
# @return [Boolean] true if valid, false otherwise
|
|
23
|
+
def valid?
|
|
24
|
+
@errors = []
|
|
25
|
+
|
|
26
|
+
validate_code_a
|
|
27
|
+
validate_code_b
|
|
28
|
+
validate_timeout
|
|
29
|
+
|
|
30
|
+
@errors.empty?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns validation errors as a hash
|
|
34
|
+
# @return [Hash] errors grouped by field
|
|
35
|
+
def error_messages
|
|
36
|
+
{
|
|
37
|
+
code_a: @errors.select { |e| e.include?('Code A') },
|
|
38
|
+
code_b: @errors.select { |e| e.include?('Code B') },
|
|
39
|
+
timeout: @errors.select { |e| e.include?('Timeout') },
|
|
40
|
+
general: @errors.reject { |e| e.include?('Code A') || e.include?('Code B') || e.include?('Timeout') }
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns all errors as a flat array
|
|
45
|
+
# @return [Array<String>] all error messages
|
|
46
|
+
def all_errors
|
|
47
|
+
@errors
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def validate_code_a
|
|
53
|
+
if @code_a.nil? || @code_a.strip.empty?
|
|
54
|
+
@errors << 'Code A is required and cannot be empty'
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def validate_code_b
|
|
59
|
+
if @code_b.nil? || @code_b.strip.empty?
|
|
60
|
+
@errors << 'Code B is required and cannot be empty'
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def validate_timeout
|
|
65
|
+
unless @timeout.is_a?(Numeric)
|
|
66
|
+
@errors << 'Timeout must be a number'
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if @timeout < MIN_TIMEOUT
|
|
71
|
+
@errors << "Timeout must be at least #{MIN_TIMEOUT} second(s)"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if @timeout > MAX_TIMEOUT
|
|
75
|
+
@errors << "Timeout cannot exceed #{MAX_TIMEOUT} seconds"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TheMechanic2
|
|
4
|
+
# Model for formatting and exporting benchmark results
|
|
5
|
+
# Provides JSON and Markdown export capabilities
|
|
6
|
+
class BenchmarkResult
|
|
7
|
+
attr_reader :code_a_metrics, :code_b_metrics, :winner, :performance_ratio, :summary
|
|
8
|
+
|
|
9
|
+
def initialize(data)
|
|
10
|
+
@code_a_metrics = data[:code_a_metrics]
|
|
11
|
+
@code_b_metrics = data[:code_b_metrics]
|
|
12
|
+
@winner = data[:winner]
|
|
13
|
+
@performance_ratio = data[:performance_ratio]
|
|
14
|
+
@summary = data[:summary]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Exports results as JSON
|
|
18
|
+
# @return [String] JSON representation of results
|
|
19
|
+
def to_json(*_args)
|
|
20
|
+
{
|
|
21
|
+
code_a_metrics: @code_a_metrics,
|
|
22
|
+
code_b_metrics: @code_b_metrics,
|
|
23
|
+
winner: @winner,
|
|
24
|
+
performance_ratio: @performance_ratio,
|
|
25
|
+
summary: @summary,
|
|
26
|
+
timestamp: Time.now.iso8601
|
|
27
|
+
}.to_json
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Exports results as Markdown
|
|
31
|
+
# @return [String] Markdown formatted results
|
|
32
|
+
def to_markdown
|
|
33
|
+
<<~MARKDOWN
|
|
34
|
+
# Ruby Benchmark Results
|
|
35
|
+
|
|
36
|
+
**Winner:** #{winner_text}
|
|
37
|
+
|
|
38
|
+
**Summary:** #{@summary}
|
|
39
|
+
|
|
40
|
+
## Performance Metrics
|
|
41
|
+
|
|
42
|
+
| Metric | Code A | Code B |
|
|
43
|
+
|--------|--------|--------|
|
|
44
|
+
| IPS (iterations/sec) | #{format_number(@code_a_metrics[:ips])} | #{format_number(@code_b_metrics[:ips])} |
|
|
45
|
+
| Standard Deviation | #{format_number(@code_a_metrics[:stddev])} | #{format_number(@code_b_metrics[:stddev])} |
|
|
46
|
+
| Objects Allocated | #{format_number(@code_a_metrics[:objects])} | #{format_number(@code_b_metrics[:objects])} |
|
|
47
|
+
| Memory (MB) | #{format_number(@code_a_metrics[:memory_mb])} | #{format_number(@code_b_metrics[:memory_mb])} |
|
|
48
|
+
| Execution Time (sec) | #{format_number(@code_a_metrics[:execution_time])} | #{format_number(@code_b_metrics[:execution_time])} |
|
|
49
|
+
|
|
50
|
+
## Analysis
|
|
51
|
+
|
|
52
|
+
#{analysis_text}
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
*Generated at #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}*
|
|
57
|
+
MARKDOWN
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns a hash representation of the results
|
|
61
|
+
# @return [Hash] results as a hash
|
|
62
|
+
def to_h
|
|
63
|
+
{
|
|
64
|
+
code_a_metrics: @code_a_metrics,
|
|
65
|
+
code_b_metrics: @code_b_metrics,
|
|
66
|
+
winner: @winner,
|
|
67
|
+
performance_ratio: @performance_ratio,
|
|
68
|
+
summary: @summary
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def winner_text
|
|
75
|
+
case @winner
|
|
76
|
+
when 'code_a'
|
|
77
|
+
"Code A (#{@performance_ratio}× faster)"
|
|
78
|
+
when 'code_b'
|
|
79
|
+
"Code B (#{@performance_ratio}× faster)"
|
|
80
|
+
when 'tie'
|
|
81
|
+
'Tie (similar performance)'
|
|
82
|
+
else
|
|
83
|
+
'Unknown'
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def analysis_text
|
|
88
|
+
lines = []
|
|
89
|
+
|
|
90
|
+
# Performance analysis
|
|
91
|
+
if @winner == 'tie'
|
|
92
|
+
lines << "Both code snippets have similar performance characteristics."
|
|
93
|
+
else
|
|
94
|
+
faster = @winner == 'code_a' ? 'Code A' : 'Code B'
|
|
95
|
+
slower = @winner == 'code_a' ? 'Code B' : 'Code A'
|
|
96
|
+
lines << "#{faster} is significantly faster than #{slower} by a factor of #{@performance_ratio}×."
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Memory analysis
|
|
100
|
+
memory_diff = (@code_a_metrics[:memory_mb] - @code_b_metrics[:memory_mb]).abs
|
|
101
|
+
if memory_diff > 0.01 # More than 0.01 MB difference
|
|
102
|
+
less_memory = @code_a_metrics[:memory_mb] < @code_b_metrics[:memory_mb] ? 'Code A' : 'Code B'
|
|
103
|
+
lines << "#{less_memory} uses less memory."
|
|
104
|
+
else
|
|
105
|
+
lines << "Both snippets have similar memory usage."
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Object allocation analysis
|
|
109
|
+
obj_diff = (@code_a_metrics[:objects] - @code_b_metrics[:objects]).abs
|
|
110
|
+
if obj_diff > 10 # More than 10 objects difference
|
|
111
|
+
fewer_objects = @code_a_metrics[:objects] < @code_b_metrics[:objects] ? 'Code A' : 'Code B'
|
|
112
|
+
lines << "#{fewer_objects} allocates fewer objects."
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
lines.join("\n\n")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def format_number(num)
|
|
119
|
+
return '0' if num.nil?
|
|
120
|
+
|
|
121
|
+
if num.is_a?(Float)
|
|
122
|
+
if num < 0.01
|
|
123
|
+
format('%.6f', num)
|
|
124
|
+
elsif num < 1
|
|
125
|
+
format('%.4f', num)
|
|
126
|
+
elsif num < 100
|
|
127
|
+
format('%.2f', num)
|
|
128
|
+
else
|
|
129
|
+
num.round.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
|
130
|
+
end
|
|
131
|
+
else
|
|
132
|
+
num.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TheMechanic2
|
|
4
|
+
# Service for orchestrating benchmark execution
|
|
5
|
+
# Coordinates between RailsRunnerService and result formatting
|
|
6
|
+
class BenchmarkService
|
|
7
|
+
# Executes a benchmark comparison between two code snippets
|
|
8
|
+
# @param shared_setup [String] Optional setup code to run before both snippets
|
|
9
|
+
# @param code_a [String] First code snippet to benchmark
|
|
10
|
+
# @param code_b [String] Second code snippet to benchmark
|
|
11
|
+
# @param timeout [Integer] Maximum execution time per benchmark in seconds
|
|
12
|
+
# @return [Hash] Formatted benchmark results with winner and metrics
|
|
13
|
+
def run(shared_setup:, code_a:, code_b:, timeout: 30)
|
|
14
|
+
runner = RailsRunnerService.new
|
|
15
|
+
|
|
16
|
+
# Execute code_a
|
|
17
|
+
result_a = runner.execute(
|
|
18
|
+
code: code_a,
|
|
19
|
+
shared_setup: shared_setup,
|
|
20
|
+
timeout: timeout
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Execute code_b
|
|
24
|
+
result_b = runner.execute(
|
|
25
|
+
code: code_b,
|
|
26
|
+
shared_setup: shared_setup,
|
|
27
|
+
timeout: timeout
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Format and return results
|
|
31
|
+
format_results(result_a, result_b)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Formats the benchmark results and determines the winner
|
|
37
|
+
# @param result_a [Hash] Results from code_a
|
|
38
|
+
# @param result_b [Hash] Results from code_b
|
|
39
|
+
# @return [Hash] Formatted results with winner and comparison
|
|
40
|
+
def format_results(result_a, result_b)
|
|
41
|
+
# Determine winner based on IPS (higher is better)
|
|
42
|
+
winner = determine_winner(result_a[:ips], result_b[:ips])
|
|
43
|
+
|
|
44
|
+
# Calculate performance ratio
|
|
45
|
+
ratio = calculate_ratio(result_a[:ips], result_b[:ips], winner)
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
code_a_metrics: {
|
|
49
|
+
ips: result_a[:ips],
|
|
50
|
+
stddev: result_a[:stddev],
|
|
51
|
+
objects: result_a[:objects],
|
|
52
|
+
memory_mb: result_a[:memory_mb],
|
|
53
|
+
execution_time: result_a[:execution_time]
|
|
54
|
+
},
|
|
55
|
+
code_b_metrics: {
|
|
56
|
+
ips: result_b[:ips],
|
|
57
|
+
stddev: result_b[:stddev],
|
|
58
|
+
objects: result_b[:objects],
|
|
59
|
+
memory_mb: result_b[:memory_mb],
|
|
60
|
+
execution_time: result_b[:execution_time]
|
|
61
|
+
},
|
|
62
|
+
winner: winner,
|
|
63
|
+
performance_ratio: ratio,
|
|
64
|
+
summary: generate_summary(winner, ratio)
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Determines which code snippet is the winner
|
|
69
|
+
# @param ips_a [Float] Iterations per second for code A
|
|
70
|
+
# @param ips_b [Float] Iterations per second for code B
|
|
71
|
+
# @return [String] 'code_a', 'code_b', or 'tie'
|
|
72
|
+
def determine_winner(ips_a, ips_b)
|
|
73
|
+
# Consider it a tie if difference is less than 5%
|
|
74
|
+
diff_percentage = ((ips_a - ips_b).abs / [ips_a, ips_b].max) * 100
|
|
75
|
+
|
|
76
|
+
if diff_percentage < 5
|
|
77
|
+
'tie'
|
|
78
|
+
elsif ips_a > ips_b
|
|
79
|
+
'code_a'
|
|
80
|
+
else
|
|
81
|
+
'code_b'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Calculates the performance ratio between the two code snippets
|
|
86
|
+
# @param ips_a [Float] Iterations per second for code A
|
|
87
|
+
# @param ips_b [Float] Iterations per second for code B
|
|
88
|
+
# @param winner [String] The winner ('code_a', 'code_b', or 'tie')
|
|
89
|
+
# @return [Float] Performance ratio (how much faster the winner is)
|
|
90
|
+
def calculate_ratio(ips_a, ips_b, winner)
|
|
91
|
+
return 1.0 if winner == 'tie'
|
|
92
|
+
|
|
93
|
+
if winner == 'code_a'
|
|
94
|
+
(ips_a / ips_b).round(2)
|
|
95
|
+
else
|
|
96
|
+
(ips_b / ips_a).round(2)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Generates a human-readable summary of the results
|
|
101
|
+
# @param winner [String] The winner
|
|
102
|
+
# @param ratio [Float] Performance ratio
|
|
103
|
+
# @return [String] Summary text
|
|
104
|
+
def generate_summary(winner, ratio)
|
|
105
|
+
case winner
|
|
106
|
+
when 'tie'
|
|
107
|
+
'Both code snippets have similar performance (within 5% difference)'
|
|
108
|
+
when 'code_a'
|
|
109
|
+
"Code A is #{ratio}× faster than Code B"
|
|
110
|
+
when 'code_b'
|
|
111
|
+
"Code B is #{ratio}× faster than Code A"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|