rails_benchmark_suite 0.3.0 → 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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +19 -0
  4. data/Gemfile.lock +27 -1
  5. data/README.md +39 -2
  6. data/bin/rails_benchmark_suite +29 -10
  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 +36 -18
  15. data/lib/rails_benchmark_suite/models/user.rb +4 -3
  16. data/lib/rails_benchmark_suite/reporter.rb +215 -5
  17. data/lib/rails_benchmark_suite/reporters/html_reporter.rb +52 -0
  18. data/lib/rails_benchmark_suite/runner.rb +54 -11
  19. data/lib/rails_benchmark_suite/schema.rb +5 -5
  20. data/lib/rails_benchmark_suite/templates/report.html.erb +187 -0
  21. data/lib/rails_benchmark_suite/version.rb +1 -1
  22. data/lib/rails_benchmark_suite/workload_runner.rb +54 -17
  23. data/lib/rails_benchmark_suite/workloads/active_record_workload.rb +6 -6
  24. data/lib/rails_benchmark_suite/workloads/cache_heft_workload.rb +1 -1
  25. data/lib/rails_benchmark_suite/workloads/image_heft_workload.rb +2 -2
  26. data/lib/rails_benchmark_suite/workloads/job_heft_workload.rb +4 -4
  27. data/lib/rails_benchmark_suite/workloads/view_heft_workload.rb +13 -21
  28. data/lib/rails_benchmark_suite.rb +3 -25
  29. data/rails_benchmark_suite.gemspec +4 -0
  30. metadata +67 -3
  31. data/lib/rails_benchmark_suite/formatter.rb +0 -206
@@ -32,6 +32,10 @@ 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"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_benchmark_suite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - RailsBenchmarkSuite Contributors
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-01-03 00:00:00.000000000 Z
10
+ date: 2026-01-04 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: benchmark-ips
@@ -121,6 +121,62 @@ dependencies:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
123
  version: '1.0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: tty-spinner
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '0.9'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.9'
138
+ - !ruby/object:Gem::Dependency
139
+ name: tty-table
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '0.12'
145
+ type: :runtime
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '0.12'
152
+ - !ruby/object:Gem::Dependency
153
+ name: tty-box
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '0.7'
159
+ type: :runtime
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '0.7'
166
+ - !ruby/object:Gem::Dependency
167
+ name: pastel
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '0.8'
173
+ type: :runtime
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '0.8'
124
180
  - !ruby/object:Gem::Dependency
125
181
  name: bundler
126
182
  requirement: !ruby/object:Gem::Requirement
@@ -196,16 +252,24 @@ files:
196
252
  - README.md
197
253
  - Rakefile
198
254
  - bin/rails_benchmark_suite
255
+ - docs/images/report_v0_3_1.png
256
+ - lib/dummy/app/models/benchmark_job.rb
257
+ - lib/dummy/app/models/benchmark_post.rb
258
+ - lib/dummy/app/models/benchmark_user.rb
259
+ - lib/dummy/app/views/rails_benchmark_suite/heft_view.html.erb
260
+ - lib/dummy/config/benchmark_database.yml
199
261
  - lib/rails_benchmark_suite.rb
262
+ - lib/rails_benchmark_suite/configuration.rb
200
263
  - lib/rails_benchmark_suite/database_manager.rb
201
264
  - lib/rails_benchmark_suite/db_setup.rb
202
- - lib/rails_benchmark_suite/formatter.rb
203
265
  - lib/rails_benchmark_suite/models/post.rb
204
266
  - lib/rails_benchmark_suite/models/simulated_job.rb
205
267
  - lib/rails_benchmark_suite/models/user.rb
206
268
  - lib/rails_benchmark_suite/reporter.rb
269
+ - lib/rails_benchmark_suite/reporters/html_reporter.rb
207
270
  - lib/rails_benchmark_suite/runner.rb
208
271
  - lib/rails_benchmark_suite/schema.rb
272
+ - lib/rails_benchmark_suite/templates/report.html.erb
209
273
  - lib/rails_benchmark_suite/version.rb
210
274
  - lib/rails_benchmark_suite/workload_runner.rb
211
275
  - lib/rails_benchmark_suite/workloads/active_record_workload.rb
@@ -1,206 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module RailsBenchmarkSuite
6
- module Formatter
7
- # ANSI Color Codes
8
- RED = "\e[31m"
9
- YELLOW = "\e[33m"
10
- GREEN = "\e[32m"
11
- BLUE = "\e[34m"
12
- BOLD = "\e[1m"
13
- RESET = "\e[0m"
14
-
15
- module_function
16
-
17
- def header(info)
18
- box_width = 60 # Internal width
19
-
20
- # Line 1: Simple text
21
- line1 = "Rails Heft Index (RHI) v0.3.0"
22
-
23
- # Line 2: Build without colors first to measure
24
- yjit_status = info[:yjit] ? 'ON' : 'OFF'
25
- yjit_hint_text = info[:yjit] ? "" : " (use RUBY_OPT=\"--yjit\")"
26
- line2_plain = "Ruby #{info[:ruby_version]} • #{info[:processors]} Cores • YJIT: #{yjit_status}#{yjit_hint_text}"
27
-
28
- # Now build with colors
29
- yjit_color = info[:yjit] ? GREEN : RED
30
- yjit_hint_colored = info[:yjit] ? "" : " #{YELLOW}(use RUBY_OPT=\"--yjit\")#{RESET}"
31
- line2 = "Ruby #{info[:ruby_version]} • #{info[:processors]} Cores • YJIT: #{yjit_color}#{yjit_status}#{RESET}#{yjit_hint_colored}"
32
-
33
- puts "\n"
34
- puts "#{BLUE}┌#{'─' * box_width}┐#{RESET}"
35
- puts "#{BLUE}│#{RESET} #{BOLD}#{line1}#{RESET}#{' ' * (box_width - 2 - line1.length)}#{BLUE}│#{RESET}"
36
- puts "#{BLUE}│#{RESET} #{line2}#{' ' * (box_width - 2 - line2_plain.length)}#{BLUE}│#{RESET}"
37
- puts "#{BLUE}└#{'─' * box_width}┘#{RESET}"
38
- puts ""
39
- end
40
-
41
- def render_progress(num, total, name, state)
42
- if state == "Running"
43
- print "[#{num}/#{total}] Running #{name}... "
44
- else
45
- puts state
46
- end
47
- end
48
-
49
- def summary_with_insights(payload)
50
- results = payload[:results]
51
- total_score = payload[:total_score]
52
- tier = payload[:tier]
53
-
54
- # Add spacing and separator before table
55
- puts "\n"
56
- puts "─" * 72
57
- puts ""
58
-
59
- # Table header
60
- printf "#{BOLD}%-28s %10s %10s %10s %7s#{RESET}\n", "Workload", "1T IPS", "4T IPS", "Scaling", "Weight"
61
- puts "─" * 72
62
-
63
- # Table rows
64
- results.each do |data|
65
- report = data[:report]
66
- entries = report.entries
67
-
68
- entry_1t = entries.find { |e| e.label.include?("(1 thread)") }
69
- entry_4t = entries.find { |e| e.label.include?("(4 threads)") }
70
-
71
- ips_1t = entry_1t ? entry_1t.ips : 0
72
- ips_4t = entry_4t ? entry_4t.ips : 0
73
-
74
- scaling = ips_1t > 0 ? (ips_4t / ips_1t) : 0
75
-
76
- # Color scaling based on performance
77
- scaling_color = if scaling >= 0.6
78
- GREEN
79
- elsif scaling >= 0.3
80
- YELLOW
81
- else
82
- RED
83
- end
84
-
85
- printf "%-28s %10s %10s #{scaling_color}%9.2fx#{RESET} %7.1f\n",
86
- data[:name],
87
- humanize(ips_1t),
88
- humanize(ips_4t),
89
- scaling,
90
- data[:adjusted_weight]
91
- end
92
-
93
- # Display insights
94
- check_scaling_insights(results)
95
- check_yjit_insight
96
- check_memory_insights(results)
97
-
98
- # Display final score
99
- render_final_score(total_score)
100
-
101
- # Display tier comparison
102
- show_hardware_tier(tier)
103
- end
104
-
105
- def check_scaling_insights(results)
106
- #Extract scaling from results
107
- poor_scaling = results.select do |r|
108
- entries = r[:report].entries
109
- entry_1t = entries.find { |e| e.label.include?("(1 thread)") }
110
- entry_4t = entries.find { |e| e.label.include?("(4 threads)") }
111
- ips_1t = entry_1t ? entry_1t.ips : 0
112
- ips_4t = entry_4t ? entry_4t.ips : 0
113
- scaling = ips_1t > 0 ? (ips_4t / ips_1t) : 0
114
- scaling < 0.8
115
- end
116
-
117
- if poor_scaling.any?
118
- puts "\n💡 Insight (Scaling): Scaling below 1.0x detected."
119
- puts " This indicates SQLite lock contention or Ruby GIL saturation."
120
- end
121
- end
122
-
123
- def check_yjit_insight
124
- unless defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
125
- puts "\n💡 Insight (YJIT): YJIT is OFF."
126
- puts " Run with RUBY_OPT=\"--yjit\" for ~20% boost."
127
- end
128
- end
129
-
130
- def check_memory_insights(results)
131
- high_memory = results.select { |r| r[:memory_delta_mb] > 20 }
132
- high_memory.each do |r|
133
- puts "\n💡 Insight (Memory): High growth in #{r[:name]} (#{r[:memory_delta_mb].round(1)}MB)"
134
- puts " Suggests heavy object allocation."
135
- end
136
- end
137
-
138
- def show_hardware_tier(tier)
139
- comparison = case tier
140
- when "Entry/Dev"
141
- "📊 Performance Tier: Entry-Level (Suitable for dev/testing, may struggle with high production traffic)"
142
- when "Production-Ready"
143
- "📊 Performance Tier: Professional-Grade (Matches the throughput of dedicated production cloud instances)"
144
- else
145
- "📊 Performance Tier: High-Performance (Exceptional throughput, comparable to bare-metal or high-end workstations)"
146
- end
147
-
148
- puts "\n#{comparison}\n"
149
- end
150
-
151
- def render_final_score(score)
152
- box_width = 60 # Same as header
153
-
154
- # Build text without colors to measure
155
- score_text = "RAILS HEFT INDEX (RHI): #{score.round(0)}"
156
-
157
- # Build with colors
158
- score_colored = "#{GREEN}#{BOLD}RAILS HEFT INDEX (RHI): #{score.round(0)}#{RESET}"
159
-
160
- puts ""
161
- puts "#{BLUE}┌#{'─' * box_width}┐#{RESET}"
162
- puts "#{BLUE}│#{RESET} #{score_colored}#{' ' * (box_width - 2 - score_text.length)}#{BLUE}│#{RESET}"
163
- puts "#{BLUE}└#{'─' * box_width}┘#{RESET}"
164
- puts ""
165
- end
166
-
167
- def as_json(payload)
168
- out = {
169
- system: RailsBenchmarkSuite::Reporter.system_info,
170
- total_score: payload[:total_score].round(0),
171
- tier: payload[:tier],
172
- workloads: []
173
- }
174
-
175
- payload[:results].each do |data|
176
- entries = data[:report].entries
177
- entry_1t = entries.find { |e| e.label.include?("(1 thread)") }
178
- entry_4t = entries.find { |e| e.label.include?("(4 threads)") }
179
- ips_1t = entry_1t ? entry_1t.ips : 0
180
- ips_4t = entry_4t ? entry_4t.ips : 0
181
-
182
- out[:workloads] << {
183
- name: data[:name],
184
- adjusted_weight: data[:adjusted_weight],
185
- ips_1t: ips_1t,
186
- ips_4t: ips_4t,
187
- scaling: ips_1t > 0 ? (ips_4t / ips_1t) : 0,
188
- memory_delta_mb: data[:memory_delta_mb]
189
- }
190
- end
191
-
192
- puts out.to_json
193
- end
194
-
195
- def humanize(ips)
196
- return "0" if ips.nil? || ips == 0
197
- if ips >= 1_000_000
198
- "#{(ips / 1_000_000.0).round(1)}M"
199
- elsif ips >= 1_000
200
- "#{(ips / 1_000.0).round(1)}k"
201
- else
202
- ips.round(1).to_s
203
- end
204
- end
205
- end
206
- end