rails_benchmark_suite 0.2.9 → 0.3.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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/CHANGELOG.md +65 -0
  4. data/Gemfile.lock +29 -1
  5. data/README.md +103 -10
  6. data/bin/rails_benchmark_suite +35 -12
  7. data/docs/images/report_v0_3_1.png +0 -0
  8. data/lib/dummy/app/models/benchmark_job.rb +7 -0
  9. data/lib/dummy/app/models/benchmark_post.rb +9 -0
  10. data/lib/dummy/app/models/benchmark_user.rb +9 -0
  11. data/lib/dummy/app/views/rails_benchmark_suite/heft_view.html.erb +11 -0
  12. data/lib/dummy/config/benchmark_database.yml +16 -0
  13. data/lib/rails_benchmark_suite/configuration.rb +22 -0
  14. data/lib/rails_benchmark_suite/database_manager.rb +56 -0
  15. data/lib/rails_benchmark_suite/db_setup.rb +3 -0
  16. data/lib/rails_benchmark_suite/models/user.rb +4 -3
  17. data/lib/rails_benchmark_suite/reporter.rb +215 -5
  18. data/lib/rails_benchmark_suite/reporters/html_reporter.rb +52 -0
  19. data/lib/rails_benchmark_suite/runner.rb +46 -191
  20. data/lib/rails_benchmark_suite/schema.rb +5 -5
  21. data/lib/rails_benchmark_suite/templates/report.html.erb +187 -0
  22. data/lib/rails_benchmark_suite/version.rb +1 -1
  23. data/lib/rails_benchmark_suite/workload_runner.rb +158 -0
  24. data/lib/rails_benchmark_suite/{suites/active_record_suite.rb → workloads/active_record_workload.rb} +7 -7
  25. data/lib/rails_benchmark_suite/{suites/cache_heft_suite.rb → workloads/cache_heft_workload.rb} +2 -2
  26. data/lib/rails_benchmark_suite/{suites/image_heft_suite.rb → workloads/image_heft_workload.rb} +3 -4
  27. data/lib/rails_benchmark_suite/{suites/job_heft_suite.rb → workloads/job_heft_workload.rb} +4 -4
  28. data/lib/rails_benchmark_suite/workloads/view_heft_workload.rb +36 -0
  29. data/lib/rails_benchmark_suite.rb +3 -22
  30. data/rails_benchmark_suite.gemspec +7 -2
  31. metadata +92 -10
  32. data/lib/rails_benchmark_suite/suites/view_heft_suite.rb +0 -44
@@ -0,0 +1,187 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-bs-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Rails Benchmark Suite Report</title>
7
+ <!-- Bootstrap 5 CDN -->
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <!-- Chart.js 4 CDN -->
10
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
+
12
+ <style>
13
+ body { background-color: #121212; color: #e0e0e0; font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; }
14
+ .card { background-color: #1e1e1e; border: 1px solid #333; }
15
+ .table-dark { --bs-table-bg: #1e1e1e; --bs-table-striped-bg: #252525; }
16
+ .efficiency-high { color: #4caf50; font-weight: bold; }
17
+ .efficiency-med { color: #ffc107; font-weight: bold; }
18
+ .efficiency-low { color: #f44336; font-weight: bold; }
19
+ .header-bar { border-bottom: 2px solid #3d3d3d; padding-bottom: 20px; margin-bottom: 30px; }
20
+ </style>
21
+ </head>
22
+ <body class="container py-5">
23
+
24
+ <!-- Header -->
25
+ <div class="header-bar d-flex justify-content-between align-items-center">
26
+ <div>
27
+ <h1 class="display-5 fw-bold text-light">Rails Benchmark Suite</h1>
28
+ <p class="text-secondary mb-0">v<%= RailsBenchmarkSuite::VERSION %> Report</p>
29
+ </div>
30
+ <div class="text-end text-white-50">
31
+ <div>Ruby <%= RUBY_VERSION %></div>
32
+ <div><%= @payload[:threads] %> Threads</div>
33
+ <div><%= Time.now.strftime("%Y-%m-%d %H:%M") %></div>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Score Cards -->
38
+ <div class="row g-4 mb-5">
39
+ <div class="col-md-6">
40
+ <div class="card h-100 p-4 text-center">
41
+ <h3 class="text-secondary">RHI Score</h3>
42
+ <div class="display-1 fw-bold <%= @payload[:total_score] > 200 ? 'text-primary' : 'text-light' %>">
43
+ <%= @payload[:total_score].to_i %>
44
+ </div>
45
+ <div class="badge bg-dark border mt-2"><%= @payload[:tier] %> Tier</div>
46
+ </div>
47
+ </div>
48
+ <div class="col-md-6">
49
+ <div class="card h-100 p-4 text-center">
50
+ <h3 class="text-secondary">Avg. Efficiency</h3>
51
+ <%
52
+ total_eff = @payload[:results].sum do |r|
53
+ entry_1t = r[:report].entries.find { |e| e.label.include?("(1 thread)") }
54
+ entry_mt = r[:report].entries.find { |e| e.label.match?(/\(\d+ threads\)/) }
55
+ next 0 unless entry_1t && entry_mt
56
+ (entry_mt.ips / (entry_1t.ips * @payload[:threads])) * 100
57
+ end
58
+ avg_eff = total_eff / [@payload[:results].size, 1].max
59
+ color_class = avg_eff > 75 ? 'efficiency-high' : (avg_eff > 50 ? 'efficiency-med' : 'efficiency-low')
60
+ %>
61
+ <div class="display-1 <%= color_class %>">
62
+ <%= avg_eff.round(1) %>%
63
+ </div>
64
+ <small class="text-muted">Target: > 80% linear scaling</small>
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- Chart -->
70
+ <div class="card mb-5">
71
+ <div class="card-header bg-transparent border-bottom border-secondary">
72
+ <h5 class="mb-0">Scaling Curve (1T vs MaxT)</h5>
73
+ </div>
74
+ <div class="card-body">
75
+ <canvas id="scalingChart" style="max-height: 400px;"></canvas>
76
+ </div>
77
+ </div>
78
+
79
+ <!-- Detailed Table -->
80
+ <div class="card">
81
+ <div class="card-header bg-transparent border-bottom border-secondary">
82
+ <h5 class="mb-0">Detailed Metrics</h5>
83
+ </div>
84
+ <div class="table-responsive">
85
+ <table class="table table-dark table-striped table-hover mb-0 align-middle">
86
+ <thead>
87
+ <tr>
88
+ <th style="width: 30%">Workload</th>
89
+ <th class="text-end">1T IPS</th>
90
+ <th class="text-end">MaxT IPS</th>
91
+ <th class="text-end">Scaling (x)</th>
92
+ <th class="text-end">Efficiency</th>
93
+ <th class="text-end">Weight</th>
94
+ </tr>
95
+ </thead>
96
+ <tbody>
97
+ <% @payload[:results].each do |res| %>
98
+ <%
99
+ entry_1t = res[:report].entries.find { |e| e.label.include?("(1 thread)") }
100
+ entry_mt = res[:report].entries.find { |e| e.label.match?(/\(\d+ threads\)/) }
101
+ ips_1t = entry_1t ? entry_1t.ips : 0
102
+ ips_mt = entry_mt ? entry_mt.ips : 0
103
+
104
+ scaling = ips_1t > 0 ? (ips_mt / ips_1t) : 0
105
+ efficiency = (ips_1t.to_f > 0) ? (ips_mt.to_f / (ips_1t * @payload[:threads])) * 100 : 0.0
106
+
107
+ eff_color = efficiency > 75 ? 'efficiency-high' : (efficiency > 50 ? 'efficiency-med' : 'efficiency-low')
108
+ scale_color = scaling >= 1.0 ? 'text-success' : 'text-danger'
109
+ %>
110
+ <tr>
111
+ <td class="fw-medium"><%= res[:name] %></td>
112
+ <td class="text-end font-monospace"><%= ips_1t.round(1) %></td>
113
+ <td class="text-end font-monospace"><%= ips_mt.round(1) %></td>
114
+ <td class="text-end font-monospace <%= scale_color %>"><%= "%.2fx" % scaling %></td>
115
+ <td class="text-end font-monospace <%= eff_color %>"><%= efficiency.round(1) %>%</td>
116
+ <td class="text-end text-secondary"><%= res[:adjusted_weight].round(2) %></td>
117
+ </tr>
118
+ <% end %>
119
+ </tbody>
120
+ </table>
121
+ </div>
122
+ </div>
123
+
124
+ <!-- JavaScript Injection -->
125
+ <script>
126
+ const CHART_DATA = <%= @chart_payload %>;
127
+
128
+ document.addEventListener('DOMContentLoaded', () => {
129
+ const ctx = document.getElementById('scalingChart');
130
+
131
+ // Data is already prepared in Ruby
132
+ const labels = CHART_DATA.labels;
133
+ const data1T = CHART_DATA.data_1t;
134
+ const dataMaxT = CHART_DATA.data_mt;
135
+
136
+ // Color logic for MaxT bars (Red if bad scaling)
137
+ const backgroundColors = dataMaxT.map((val, index) => {
138
+ const ips1 = data1T[index];
139
+ const scaling = ips1 > 0 ? val / ips1 : 0;
140
+ return scaling < 0.8 ? 'rgba(239, 83, 80, 0.8)' : 'rgba(156, 39, 176, 0.8)';
141
+ });
142
+
143
+ new Chart(ctx, {
144
+ type: 'bar',
145
+ data: {
146
+ labels: labels,
147
+ datasets: [
148
+ {
149
+ label: '1-Thread IPS',
150
+ data: data1T,
151
+ backgroundColor: 'rgba(33, 150, 243, 0.8)',
152
+ borderColor: 'rgba(33, 150, 243, 1)',
153
+ borderWidth: 1
154
+ },
155
+ {
156
+ label: '<%= @payload[:threads] %>-Thread IPS',
157
+ data: dataMaxT,
158
+ backgroundColor: backgroundColors,
159
+ borderColor: backgroundColors.map(c => c.replace('0.8)', '1)')),
160
+ borderWidth: 1
161
+ }
162
+ ]
163
+ },
164
+ options: {
165
+ responsive: true,
166
+ maintainAspectRatio: false,
167
+ scales: {
168
+ y: {
169
+ beginAtZero: true,
170
+ grid: { color: '#333' },
171
+ ticks: { color: '#aaa' },
172
+ title: { display: true, text: 'Iterations Per Second (IPS)', color: '#aaa' }
173
+ },
174
+ x: {
175
+ grid: { display: false },
176
+ ticks: { color: '#fff' }
177
+ }
178
+ },
179
+ plugins: {
180
+ legend: { labels: { color: '#fff' } }
181
+ }
182
+ }
183
+ });
184
+ });
185
+ </script>
186
+ </body>
187
+ </html>
@@ -1,3 +1,3 @@
1
1
  module RailsBenchmarkSuite
2
- VERSION = "0.2.9"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark/ips"
4
+ require "get_process_mem"
5
+ require "tty-spinner"
6
+
7
+ module RailsBenchmarkSuite
8
+ class WorkloadRunner
9
+ # Base weights for each workload
10
+ BASE_WEIGHTS = {
11
+ "Active Record Heft" => 0.4,
12
+ "View Heft" => 0.2,
13
+ "Solid Queue Heft" => 0.2,
14
+ "Cache Heft" => 0.1,
15
+ "Image Heft" => 0.1
16
+ }.freeze
17
+
18
+ def initialize(workloads, options: {}, show_progress: true)
19
+ @workloads = workloads
20
+ @options = options
21
+ @threads = options[:threads] || 4
22
+ @profile_mode = options[:profile] || false
23
+ @show_progress = show_progress
24
+ end
25
+
26
+ def execute
27
+ if @profile_mode && @show_progress
28
+ puts "\nRunning Scaling Diagnostic (Profile Mode)..."
29
+ elsif @show_progress
30
+ puts "\n⏳ Running Benchmarks..."
31
+ end
32
+
33
+ # Initializing MultiSpinner if progress is enabled
34
+ multi_spinner = TTY::Spinner::Multi.new("[:spinner] Running Workloads", format: :dots) if @show_progress
35
+
36
+ # Run all workloads and collect results
37
+ results = @workloads.map do |w|
38
+ spinner = nil
39
+ if @show_progress
40
+ spinner = multi_spinner.register("[:spinner] #{w[:name]}", format: :dots)
41
+ spinner.auto_spin
42
+ end
43
+
44
+ result = run_single_workload(w, spinner)
45
+
46
+ if @show_progress && spinner
47
+ ips_1t = result[:report].entries.find { |e| e.label.include?("(1 thread)") }&.ips || 0
48
+ ips_mt = result[:report].entries.find { |e| e.label.include?("(#{@threads} threads)") }&.ips || 0
49
+
50
+ # Dynamic Success Message without duplicate name
51
+ success_msg = " ... #{Reporter.humanize(ips_1t)} IPS (1T) | #{Reporter.humanize(ips_mt)} IPS (#{@threads}T)"
52
+ spinner.success(success_msg)
53
+ end
54
+
55
+ result
56
+ end
57
+
58
+ # Calculate normalized weights
59
+ weight_pool = results.sum { |r| BASE_WEIGHTS[r[:name]] || 0 }
60
+
61
+ results.each do |r|
62
+ base_weight = BASE_WEIGHTS[r[:name]] || 1.0
63
+ r[:adjusted_weight] = base_weight / weight_pool
64
+ end
65
+
66
+ # Calculate total score
67
+ total_score = results.sum do |r|
68
+ entries = r[:report].entries
69
+ entry_mt = entries.find { |e| e.label.include?("(#{@threads} threads)") }
70
+ ips_mt = entry_mt ? entry_mt.ips : 0
71
+ ips_mt * r[:adjusted_weight]
72
+ end
73
+
74
+ # Determine tier
75
+ tier = if total_score < 50
76
+ "Entry/Dev"
77
+ elsif total_score < 200
78
+ "Production-Ready"
79
+ else
80
+ "High-Performance"
81
+ end
82
+
83
+ # Return complete payload
84
+ {
85
+ results: results,
86
+ total_score: total_score,
87
+ tier: tier,
88
+ threads: @threads,
89
+ profile_mode: @profile_mode
90
+ }
91
+ end
92
+
93
+ private
94
+
95
+ def run_single_workload(workload, spinner)
96
+ mem_before = GetProcessMem.new.mb
97
+
98
+ # Run benchmark
99
+ report = Benchmark.ips do |x|
100
+ # Silence output to allow spinner to own the UI
101
+ x.config(:time => 5, :warmup => 2, :quiet => true)
102
+
103
+ # Single Threaded
104
+ x.report("#{workload[:name]} (1 thread)") do
105
+ with_retries { workload[:block].call }
106
+ end
107
+
108
+ # Multi Threaded
109
+ x.report("#{workload[:name]} (#{@threads} threads)") do
110
+ threads = @threads.times.map do
111
+ Thread.new do
112
+ ActiveRecord::Base.connection_pool.with_connection do
113
+ with_retries { workload[:block].call }
114
+ end
115
+ end
116
+ end
117
+ threads.each(&:join)
118
+ end
119
+
120
+ # x.compare! removed to prevent STDOUT pollution
121
+ end
122
+
123
+ mem_after = GetProcessMem.new.mb
124
+
125
+ # Calculate Scaling Efficiency if in profile mode
126
+ # Efficiency = (Multi Score / (Single Score * Threads)) * 100
127
+ entries = report.entries
128
+ entry_1t = entries.find { |e| e.label.include?("(1 thread)") }
129
+ entry_mt = entries.find { |e| e.label.include?("(#{@threads} threads)") }
130
+
131
+ efficiency = 0.0
132
+ if entry_1t && entry_mt && entry_1t.ips > 0
133
+ single_score = entry_1t.ips
134
+ multi_score = entry_mt.ips
135
+ efficiency = (multi_score / (single_score * @threads)) * 100
136
+ end
137
+
138
+ {
139
+ name: workload[:name],
140
+ report: report,
141
+ memory_delta_mb: mem_after - mem_before,
142
+ efficiency: efficiency
143
+ }
144
+ end
145
+
146
+ def with_retries
147
+ yield
148
+ rescue ActiveRecord::StatementInvalid => e
149
+ if e.message =~ /locked/i
150
+ ActiveRecord::Base.connection.reset!
151
+ sleep(rand(0.01..0.05))
152
+ retry
153
+ else
154
+ raise e
155
+ end
156
+ end
157
+ end
158
+ end
@@ -2,13 +2,13 @@
2
2
 
3
3
  require "active_record"
4
4
 
5
- # Benchmark Suite
6
- RailsBenchmarkSuite.register_suite("Active Record Heft", weight: 0.4) do
5
+ # Benchmark Workload
6
+ RailsBenchmarkSuite::Runner.register_workload("Active Record Heft", weight: 0.4) do
7
7
  # Workload: Create User with Posts, Join Query, Update
8
8
  # Use transaction rollback to keep the DB clean and avoid costly destroy callbacks
9
9
  ActiveRecord::Base.transaction do
10
10
  # 1. Create - with unique email per thread
11
- user = RailsBenchmarkSuite::Models::User.create!(
11
+ user = RailsBenchmarkSuite::Dummy::BenchmarkUser.create!(
12
12
  name: "Benchmark User",
13
13
  email: "test-#{Thread.current.object_id}@example.com"
14
14
  )
@@ -20,10 +20,10 @@ RailsBenchmarkSuite.register_suite("Active Record Heft", weight: 0.4) do
20
20
 
21
21
  # 3. Complex Query (Join + Order)
22
22
  # Unloading the relation to force execution
23
- RailsBenchmarkSuite::Models::User.joins(:posts)
24
- .where(users: { id: user.id })
25
- .where("posts.views >= ?", 0)
26
- .order("posts.created_at DESC")
23
+ RailsBenchmarkSuite::Dummy::BenchmarkUser.joins(:posts)
24
+ .where(benchmark_users: { id: user.id })
25
+ .where("benchmark_posts.views >= ?", 0)
26
+ .order("benchmark_posts.created_at DESC")
27
27
  .to_a
28
28
 
29
29
  # 4. Update
@@ -3,8 +3,8 @@
3
3
  require "active_support/cache"
4
4
  require "securerandom"
5
5
 
6
- # Benchmark Suite
7
- RailsBenchmarkSuite.register_suite("Cache Heft", weight: 0.1) do
6
+ # Benchmark Workload
7
+ RailsBenchmarkSuite::Runner.register_workload("Cache Heft", weight: 0.1) do
8
8
  # Simulate SolidCache using MemoryStore
9
9
  @cache ||= ActiveSupport::Cache::MemoryStore.new
10
10
 
@@ -11,7 +11,7 @@ begin
11
11
  SAMPLE_IMAGE = File.join(ASSET_DIR, "sample.jpg")
12
12
 
13
13
  # Only register if vips is actually available
14
- RailsBenchmarkSuite.register_suite("Image Heft", weight: 0.1) do
14
+ RailsBenchmarkSuite::Runner.register_workload("Image Heft", weight: 0.1) do
15
15
  # Gracefully handle missing dependencies
16
16
  if File.exist?(SAMPLE_IMAGE)
17
17
  ImageProcessing::Vips
@@ -25,7 +25,6 @@ begin
25
25
  end
26
26
 
27
27
  rescue LoadError, StandardError
28
- # Don't register the suite at all if vips is unavailable
29
- puts "⚠️ Skipping Image Heft: libvips not available. Install with: brew install vips (macOS)"
28
+ # Don't register the workload at all if vips is unavailable
29
+ puts "\n⚠️ Skipping Image Workload: libvips not available. Install with: 'brew install vips' (macOS) or 'sudo apt install libvips-dev' (Linux)\n\n"
30
30
  end
31
-
@@ -4,12 +4,12 @@ require "active_record"
4
4
  require "json"
5
5
 
6
6
 
7
- RailsBenchmarkSuite.register_suite("Solid Queue Heft", weight: 0.2) do
7
+ RailsBenchmarkSuite::Runner.register_workload("Solid Queue Heft", weight: 0.2) do
8
8
  # Simulation: Enqueue 100 jobs, then work them off
9
9
 
10
10
  # 1. Enqueue Loop
11
11
  100.times do |i|
12
- RailsBenchmarkSuite::Models::SimulatedJob.create!(
12
+ RailsBenchmarkSuite::Dummy::BenchmarkJob.create!(
13
13
  queue_name: "default",
14
14
  arguments: { job_id: i, payload: "x" * 100 }.to_json,
15
15
  scheduled_at: Time.now
@@ -23,12 +23,12 @@ RailsBenchmarkSuite.register_suite("Solid Queue Heft", weight: 0.2) do
23
23
  # Transactional polling
24
24
  ActiveRecord::Base.transaction do
25
25
  # Fetch batch
26
- jobs = RailsBenchmarkSuite::Models::SimulatedJob.where("scheduled_at <= ?", Time.now).order(:created_at).limit(10)
26
+ jobs = RailsBenchmarkSuite::Dummy::BenchmarkJob.where("scheduled_at <= ?", Time.now).order(:created_at).limit(10)
27
27
 
28
28
  if jobs.any?
29
29
  # Simulate processing time and delete
30
30
  ids = jobs.map(&:id)
31
- RailsBenchmarkSuite::Models::SimulatedJob.where(id: ids).delete_all
31
+ RailsBenchmarkSuite::Dummy::BenchmarkJob.where(id: ids).delete_all
32
32
  processed = true
33
33
  end
34
34
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view"
4
+ require "ostruct"
5
+
6
+ # Benchmark Workload
7
+
8
+
9
+ # Helper for the workload - Mixed into ActionView::Base instance automatically by Rails usually,
10
+ # but here we might need to include it or just rely on standard helpers if ActionView loads them.
11
+ # The template uses number_with_delimiter which is standard.
12
+ module RailsBenchmarkSuiteNumberHelper
13
+ # No-op or keep provided helper if standard library fails in isolation
14
+ end
15
+
16
+ RailsBenchmarkSuite::Runner.register_workload("View Heft", weight: 0.2) do
17
+ # Setup context once
18
+ @view_renderer ||= begin
19
+ # Use the "Dummy" app views folder
20
+ views_path = File.expand_path("../../dummy/app/views", __dir__)
21
+ lookup_context = ActionView::LookupContext.new([views_path])
22
+ ActionView::Base.with_empty_template_cache.new(lookup_context, {}, nil)
23
+ end
24
+
25
+ # Workload: Render template from file
26
+ # Previously inline, now isolated in lib/dummy/app/views
27
+
28
+
29
+ # Dummy Objects
30
+ user = OpenStruct.new(name: "Speedy")
31
+ posts = 100.times.map { |i| OpenStruct.new(title: "Post #{i}", body: "Content " * 10, views: i * 1000) }
32
+
33
+ # Execution
34
+ # Render the namespaced template 'rails_benchmark_suite/heft_view'
35
+ @view_renderer.render(template: "rails_benchmark_suite/heft_view", locals: { user: user, posts: posts })
36
+ end
@@ -1,27 +1,8 @@
1
- # frozen_string_literal: true
2
-
3
- require "concurrent"
4
1
  require "rails_benchmark_suite/version"
5
- require "rails_benchmark_suite/reporter"
6
2
  require "rails_benchmark_suite/runner"
7
- require "rails_benchmark_suite/db_setup"
8
- require "rails_benchmark_suite/schema"
9
- require "rails_benchmark_suite/models/user"
10
- require "rails_benchmark_suite/models/post"
11
- require "rails_benchmark_suite/models/simulated_job"
3
+ require "rails_benchmark_suite/reporter"
4
+ require "rails_benchmark_suite/configuration"
12
5
 
13
6
  module RailsBenchmarkSuite
14
- @suites = []
15
-
16
- def self.register_suite(name, weight: 1.0, &block)
17
- @suites << { name: name, weight: weight, block: block }
18
- end
19
-
20
- def self.run(json: false)
21
- # Load suites
22
- Dir[File.join(__dir__, "rails_benchmark_suite", "suites", "*.rb")].each { |f| require f }
23
-
24
- runner = Runner.new(@suites, json: json)
25
- runner.run
26
- end
7
+ class Error < StandardError; end
27
8
  end
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["RailsBenchmarkSuite Contributors"]
9
9
  spec.email = ["team@rails.org"]
10
10
 
11
- spec.summary = "Rails-style functionality & performance benchmark tool"
12
- spec.description = "Measures the 'Heft' (processing power) of a machine using realistic Rails workloads."
11
+ spec.summary = "Rails Heft Index (RHI) - Hardware benchmarking using realistic workloads"
12
+ spec.description = "Measures the Rails Heft Index (RHI), a weighted performance score based on realistic Rails 8+ workloads across Active Record, caching, views, jobs, and image processing."
13
13
  spec.homepage = "https://github.com/overnet/rails_benchmark_suite"
14
14
  spec.license = "MIT"
15
15
 
@@ -32,10 +32,15 @@ Gem::Specification.new do |spec|
32
32
  spec.add_dependency "sqlite3", "~> 2.8"
33
33
  spec.add_dependency "concurrent-ruby", "~> 1.3"
34
34
  spec.add_dependency "get_process_mem", "~> 1.0"
35
+ spec.add_dependency "tty-spinner", "~> 0.9"
36
+ spec.add_dependency "tty-table", "~> 0.12"
37
+ spec.add_dependency "tty-box", "~> 0.7"
38
+ spec.add_dependency "pastel", "~> 0.8"
35
39
 
36
40
  spec.add_development_dependency "bundler", "~> 2.5"
37
41
  spec.add_development_dependency "rake", "~> 13.0"
38
42
  spec.add_development_dependency "minitest", "~> 5.0"
43
+ spec.add_development_dependency "ostruct", "~> 0.6"
39
44
 
40
45
  spec.required_ruby_version = ">= 3.4.0"
41
46
  end