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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile.lock +27 -1
- data/README.md +39 -2
- data/bin/rails_benchmark_suite +29 -10
- data/docs/images/report_v0_3_1.png +0 -0
- data/lib/dummy/app/models/benchmark_job.rb +7 -0
- data/lib/dummy/app/models/benchmark_post.rb +9 -0
- data/lib/dummy/app/models/benchmark_user.rb +9 -0
- data/lib/dummy/app/views/rails_benchmark_suite/heft_view.html.erb +11 -0
- data/lib/dummy/config/benchmark_database.yml +16 -0
- data/lib/rails_benchmark_suite/configuration.rb +22 -0
- data/lib/rails_benchmark_suite/database_manager.rb +36 -18
- data/lib/rails_benchmark_suite/models/user.rb +4 -3
- data/lib/rails_benchmark_suite/reporter.rb +215 -5
- data/lib/rails_benchmark_suite/reporters/html_reporter.rb +52 -0
- data/lib/rails_benchmark_suite/runner.rb +54 -11
- data/lib/rails_benchmark_suite/schema.rb +5 -5
- data/lib/rails_benchmark_suite/templates/report.html.erb +187 -0
- data/lib/rails_benchmark_suite/version.rb +1 -1
- data/lib/rails_benchmark_suite/workload_runner.rb +54 -17
- data/lib/rails_benchmark_suite/workloads/active_record_workload.rb +6 -6
- data/lib/rails_benchmark_suite/workloads/cache_heft_workload.rb +1 -1
- data/lib/rails_benchmark_suite/workloads/image_heft_workload.rb +2 -2
- data/lib/rails_benchmark_suite/workloads/job_heft_workload.rb +4 -4
- data/lib/rails_benchmark_suite/workloads/view_heft_workload.rb +13 -21
- data/lib/rails_benchmark_suite.rb +3 -25
- data/rails_benchmark_suite.gemspec +4 -0
- metadata +67 -3
- 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.
|
|
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-
|
|
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
|