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.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/CHANGELOG.md +65 -0
- data/Gemfile.lock +29 -1
- data/README.md +103 -10
- data/bin/rails_benchmark_suite +35 -12
- 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 +56 -0
- data/lib/rails_benchmark_suite/db_setup.rb +3 -0
- 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 +46 -191
- 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 +158 -0
- data/lib/rails_benchmark_suite/{suites/active_record_suite.rb → workloads/active_record_workload.rb} +7 -7
- data/lib/rails_benchmark_suite/{suites/cache_heft_suite.rb → workloads/cache_heft_workload.rb} +2 -2
- data/lib/rails_benchmark_suite/{suites/image_heft_suite.rb → workloads/image_heft_workload.rb} +3 -4
- data/lib/rails_benchmark_suite/{suites/job_heft_suite.rb → workloads/job_heft_workload.rb} +4 -4
- data/lib/rails_benchmark_suite/workloads/view_heft_workload.rb +36 -0
- data/lib/rails_benchmark_suite.rb +3 -22
- data/rails_benchmark_suite.gemspec +7 -2
- metadata +92 -10
- data/lib/rails_benchmark_suite/suites/view_heft_suite.rb +0 -44
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
4
|
+
require "tty-table"
|
|
5
|
+
require "tty-box"
|
|
6
|
+
require "pastel"
|
|
7
|
+
require "tty-cursor"
|
|
8
|
+
|
|
3
9
|
module RailsBenchmarkSuite
|
|
4
10
|
module Reporter
|
|
5
11
|
module_function
|
|
6
12
|
|
|
13
|
+
def pastel
|
|
14
|
+
@pastel ||= Pastel.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def cursor
|
|
18
|
+
@cursor ||= TTY::Cursor
|
|
19
|
+
end
|
|
20
|
+
|
|
7
21
|
def system_info
|
|
8
22
|
{
|
|
9
23
|
ruby_version: RUBY_VERSION,
|
|
10
24
|
platform: RUBY_PLATFORM,
|
|
11
|
-
processors: Concurrent.processor_count,
|
|
12
|
-
libvips: libvips?,
|
|
25
|
+
processors: defined?(Etc) ? Etc.nprocessors : Concurrent.processor_count,
|
|
13
26
|
yjit: yjit_enabled?
|
|
14
27
|
}
|
|
15
28
|
end
|
|
@@ -18,9 +31,206 @@ module RailsBenchmarkSuite
|
|
|
18
31
|
defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
|
|
19
32
|
end
|
|
20
33
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
def header(info)
|
|
35
|
+
print cursor.hide
|
|
36
|
+
# Build YJIT Status
|
|
37
|
+
yjit_status = info[:yjit] ? pastel.green("ON") : pastel.red("OFF")
|
|
38
|
+
yjit_hint = info[:yjit] ? "" : " (use RUBY_OPT=\"--yjit\")"
|
|
39
|
+
|
|
40
|
+
content = [
|
|
41
|
+
"System: #{info[:processors]} Cores | Ruby #{info[:ruby_version]}",
|
|
42
|
+
"DB: #{info[:db_mode] || 'SQLite (Memory)'} | YJIT: #{yjit_status}#{yjit_hint}"
|
|
43
|
+
].join("\n")
|
|
44
|
+
|
|
45
|
+
print TTY::Box.frame(
|
|
46
|
+
width: 80,
|
|
47
|
+
title: { top_left: " Rails Benchmark Suite v#{RailsBenchmarkSuite::VERSION} " },
|
|
48
|
+
padding: 1,
|
|
49
|
+
style: {
|
|
50
|
+
fg: :white,
|
|
51
|
+
border: { fg: :bright_blue }
|
|
52
|
+
}
|
|
53
|
+
) { content }
|
|
54
|
+
puts ""
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def render(payload)
|
|
58
|
+
results = payload[:results]
|
|
59
|
+
total_score = payload[:total_score]
|
|
60
|
+
tier = payload[:tier]
|
|
61
|
+
threads = payload[:threads] || 4
|
|
62
|
+
|
|
63
|
+
puts ""
|
|
64
|
+
|
|
65
|
+
# 1. Comparison Table
|
|
66
|
+
rows = results.map do |data|
|
|
67
|
+
report = data[:report]
|
|
68
|
+
entries = report.entries
|
|
69
|
+
|
|
70
|
+
entry_1t = entries.find { |e| e.label.include?("(1 thread)") }
|
|
71
|
+
entry_mt = entries.find { |e| e.label.match?(/\(\d+ threads\)/) }
|
|
72
|
+
|
|
73
|
+
ips_1t = entry_1t ? entry_1t.ips : 0
|
|
74
|
+
ips_mt = entry_mt ? entry_mt.ips : 0
|
|
75
|
+
|
|
76
|
+
scaling = ips_1t > 0 ? (ips_mt / ips_1t) : 0
|
|
77
|
+
efficiency = (ips_mt / (ips_1t * threads)) * 100 if ips_1t > 0 && threads > 0
|
|
78
|
+
efficiency ||= 0
|
|
79
|
+
|
|
80
|
+
# Color coding
|
|
81
|
+
eff_color = if efficiency >= 75
|
|
82
|
+
:green
|
|
83
|
+
elsif efficiency >= 50
|
|
84
|
+
:yellow
|
|
85
|
+
else
|
|
86
|
+
:red
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Colorize scaling (Simple heuristic per user request)
|
|
90
|
+
scale_str = "%.2fx" % scaling
|
|
91
|
+
if scaling >= 1.0
|
|
92
|
+
scale_str = pastel.green(scale_str)
|
|
93
|
+
else
|
|
94
|
+
scale_str = pastel.red(scale_str)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
[
|
|
98
|
+
data[:name],
|
|
99
|
+
humanize(ips_1t),
|
|
100
|
+
humanize(ips_mt),
|
|
101
|
+
scale_str,
|
|
102
|
+
pastel.decorate("#{efficiency.round(1)}%", eff_color),
|
|
103
|
+
data[:adjusted_weight].round(2)
|
|
104
|
+
]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
table = TTY::Table.new(
|
|
108
|
+
header: ["Workload", "1T IPS", "MaxT IPS", "Scaling", "Efficiency", "Weight"],
|
|
109
|
+
rows: rows
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
puts table.render(:unicode, padding: [0, 1]) do |renderer|
|
|
113
|
+
renderer.border.separator = :each_row
|
|
114
|
+
renderer.border.style = :blue
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# 2. Insights List
|
|
118
|
+
puts ""
|
|
119
|
+
check_scaling_insights(results)
|
|
120
|
+
check_yjit_insight
|
|
121
|
+
check_memory_insights(results)
|
|
122
|
+
|
|
123
|
+
# 3. Final Score Dashboard
|
|
124
|
+
render_final_score(total_score)
|
|
125
|
+
show_hardware_tier(tier)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def check_scaling_insights(results)
|
|
129
|
+
poor_scaling = results.select do |r|
|
|
130
|
+
entries = r[:report].entries
|
|
131
|
+
entry_1t = entries.find { |e| e.label.include?("(1 thread)") }
|
|
132
|
+
entry_mt = entries.find { |e| e.label.match?(/\(\d+ threads\)/) }
|
|
133
|
+
|
|
134
|
+
ips_1t = entry_1t ? entry_1t.ips : 0
|
|
135
|
+
ips_mt = entry_mt ? entry_mt.ips : 0
|
|
136
|
+
scaling = ips_1t > 0 ? (ips_mt / ips_1t) : 0
|
|
137
|
+
scaling < 0.8
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if poor_scaling.any?
|
|
141
|
+
puts pastel.yellow.bold("💡 Insight (Scaling):") + " Scaling below 1.0x detected."
|
|
142
|
+
puts " This indicates SQLite lock contention or Ruby GIL saturation."
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def check_yjit_insight
|
|
147
|
+
unless yjit_enabled?
|
|
148
|
+
puts ""
|
|
149
|
+
puts pastel.yellow.bold("💡 Insight (YJIT):") + " YJIT is OFF."
|
|
150
|
+
puts " Run with RUBY_OPT=\"--yjit\" for ~20% boost."
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def check_memory_insights(results)
|
|
155
|
+
results.select { |r| r[:memory_delta_mb] > 20 }.each do |r|
|
|
156
|
+
puts ""
|
|
157
|
+
puts pastel.yellow.bold("💡 Insight (Memory):") + " High growth in #{r[:name]} (#{r[:memory_delta_mb].round(1)}MB)"
|
|
158
|
+
puts " Suggests heavy object allocation."
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def show_hardware_tier(tier)
|
|
163
|
+
comparison = case tier
|
|
164
|
+
when "Entry/Dev"
|
|
165
|
+
"Entry-Level (Suitable for dev/testing)"
|
|
166
|
+
when "Production-Ready"
|
|
167
|
+
"Professional-Grade (Matches dedicated instances)"
|
|
168
|
+
else
|
|
169
|
+
"High-Performance (Bare-metal speed)"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
puts ""
|
|
173
|
+
puts pastel.bold("📊 Performance Tier: ") + comparison
|
|
174
|
+
puts ""
|
|
175
|
+
print cursor.show
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def render_final_score(score)
|
|
179
|
+
score_str = "#{score.round(0)}"
|
|
180
|
+
puts ""
|
|
181
|
+
|
|
182
|
+
print TTY::Box.frame(
|
|
183
|
+
width: 40,
|
|
184
|
+
height: 5,
|
|
185
|
+
align: :center,
|
|
186
|
+
padding: 1,
|
|
187
|
+
title: { top_left: " RAILS HEFT INDEX " },
|
|
188
|
+
style: { border: { fg: :green }, fg: :green }
|
|
189
|
+
) {
|
|
190
|
+
pastel.bold(score_str)
|
|
191
|
+
}
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def as_json(payload)
|
|
195
|
+
out = {
|
|
196
|
+
system: system_info,
|
|
197
|
+
total_score: payload[:total_score].round(0),
|
|
198
|
+
tier: payload[:tier],
|
|
199
|
+
workloads: []
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
payload[:results].each do |data|
|
|
203
|
+
entries = data[:report].entries
|
|
204
|
+
entry_1t = entries.find { |e| e.label.include?("(1 thread)") }
|
|
205
|
+
entry_mt = entries.find { |e| e.label.match?(/\(\d+ threads\)/) }
|
|
206
|
+
|
|
207
|
+
ips_1t = entry_1t ? entry_1t.ips : 0
|
|
208
|
+
ips_mt = entry_mt ? entry_mt.ips : 0
|
|
209
|
+
|
|
210
|
+
out[:workloads] << {
|
|
211
|
+
name: data[:name],
|
|
212
|
+
adjusted_weight: data[:adjusted_weight],
|
|
213
|
+
ips_1t: ips_1t,
|
|
214
|
+
ips_mt: ips_mt,
|
|
215
|
+
threads: payload[:threads],
|
|
216
|
+
scaling: ips_1t > 0 ? (ips_mt / ips_1t) : 0,
|
|
217
|
+
efficiency: data[:efficiency],
|
|
218
|
+
memory_delta_mb: data[:memory_delta_mb]
|
|
219
|
+
}
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
puts out.to_json
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def humanize(ips)
|
|
226
|
+
return "0" if ips.nil? || ips == 0
|
|
227
|
+
if ips >= 1_000_000
|
|
228
|
+
"#{(ips / 1_000_000.0).round(1)}M"
|
|
229
|
+
elsif ips >= 1_000
|
|
230
|
+
"#{(ips / 1_000.0).round(1)}k"
|
|
231
|
+
else
|
|
232
|
+
ips.round(1).to_s
|
|
233
|
+
end
|
|
24
234
|
end
|
|
25
235
|
end
|
|
26
236
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
require "json"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
module RailsBenchmarkSuite
|
|
8
|
+
module Reporters
|
|
9
|
+
class HtmlReporter
|
|
10
|
+
def initialize(payload)
|
|
11
|
+
@payload = payload
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def generate
|
|
15
|
+
template_path = File.expand_path("../templates/report.html.erb", __dir__)
|
|
16
|
+
template = File.read(template_path)
|
|
17
|
+
|
|
18
|
+
# Prepare data for JS injection (Flatten complex objects to simple Hash)
|
|
19
|
+
chart_data = {
|
|
20
|
+
labels: @payload[:results].map { |r| r[:name] },
|
|
21
|
+
data_1t: [],
|
|
22
|
+
data_mt: []
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@payload[:results].each do |res|
|
|
26
|
+
entry_1t = res[:report].entries.find { |e| e.label.include?("(1 thread)") }
|
|
27
|
+
entry_mt = res[:report].entries.find { |e| e.label.match?(/\(\d+ threads\)/) }
|
|
28
|
+
|
|
29
|
+
chart_data[:data_1t] << (entry_1t ? entry_1t.ips : 0)
|
|
30
|
+
chart_data[:data_mt] << (entry_mt ? entry_mt.ips : 0)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@chart_payload = chart_data.to_json
|
|
34
|
+
|
|
35
|
+
# Render
|
|
36
|
+
html = ERB.new(template).result(binding)
|
|
37
|
+
|
|
38
|
+
# Output file
|
|
39
|
+
dir = Dir.exist?("tmp") ? "tmp" : "."
|
|
40
|
+
file_path = File.join(dir, "rails_benchmark_report.html")
|
|
41
|
+
File.write(file_path, html)
|
|
42
|
+
|
|
43
|
+
puts "\n"
|
|
44
|
+
puts "✅ HTML Report Generated!"
|
|
45
|
+
puts "📂 Location: #{File.expand_path(file_path)}"
|
|
46
|
+
puts "👉 View (Local): open '#{file_path}'"
|
|
47
|
+
puts "👉 View (Remote): scp user@server:#{File.expand_path(file_path)} ."
|
|
48
|
+
puts "\n"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -1,211 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
require_relative "database_manager"
|
|
2
|
+
require_relative "workload_runner"
|
|
3
|
+
require_relative "reporter"
|
|
4
|
+
require_relative "schema"
|
|
5
|
+
require_relative "../dummy/app/models/benchmark_user"
|
|
6
|
+
require_relative "../dummy/app/models/benchmark_post"
|
|
7
|
+
require_relative "../dummy/app/models/benchmark_job"
|
|
5
8
|
|
|
6
9
|
module RailsBenchmarkSuite
|
|
7
10
|
class Runner
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@json_output = json
|
|
11
|
-
end
|
|
11
|
+
# Registry for workloads
|
|
12
|
+
@workloads = []
|
|
12
13
|
|
|
13
|
-
def
|
|
14
|
-
@
|
|
14
|
+
def self.register_workload(name, weight: 1.0, &block)
|
|
15
|
+
@workloads << { name: name, weight: weight, block: block }
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def run
|
|
20
|
-
# Ultimate Hardening: Massive pool and timeout for zero lock contention (v0.2.9)
|
|
21
|
-
ActiveRecord::Base.establish_connection(
|
|
22
|
-
adapter: "sqlite3",
|
|
23
|
-
database: "file:heft_db?mode=memory&cache=shared",
|
|
24
|
-
pool: 50,
|
|
25
|
-
timeout: 30000
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
# The 'Busy Timeout' Hammer - force it directly on the raw connection
|
|
29
|
-
ActiveRecord::Base.connection.raw_connection.busy_timeout = 10000
|
|
30
|
-
|
|
31
|
-
# Setup Schema once safely with Mutex
|
|
32
|
-
SETUP_MUTEX.synchronize do
|
|
33
|
-
# Verify if schema already loaded by checking for a table
|
|
34
|
-
unless ActiveRecord::Base.connection.table_exists?(:users)
|
|
35
|
-
RailsBenchmarkSuite::Schema.load
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# High-Performance Pragmas for WAL + NORMAL sync
|
|
40
|
-
ActiveRecord::Base.connection.raw_connection.execute("PRAGMA journal_mode = WAL")
|
|
41
|
-
ActiveRecord::Base.connection.raw_connection.execute("PRAGMA synchronous = NORMAL")
|
|
42
|
-
ActiveRecord::Base.connection.raw_connection.execute("PRAGMA mmap_size = 268435456") # 256MB - reduce disk I/O
|
|
43
|
-
|
|
44
|
-
puts "Running RailsBenchmarkSuite Benchmarks..." unless @json_output
|
|
45
|
-
puts system_report unless @json_output
|
|
46
|
-
puts "\n" unless @json_output
|
|
47
|
-
|
|
48
|
-
results = {}
|
|
49
|
-
|
|
50
|
-
@suites.each do |suite|
|
|
51
|
-
puts "== Running Suite: #{suite[:name]} ==" unless @json_output
|
|
52
|
-
|
|
53
|
-
# Capture memory before
|
|
54
|
-
mem_before = GetProcessMem.new.mb
|
|
55
|
-
|
|
56
|
-
# Run benchmark
|
|
57
|
-
report = Benchmark.ips do |x|
|
|
58
|
-
x.config(:time => 5, :warmup => 2)
|
|
59
|
-
|
|
60
|
-
# Single Threaded
|
|
61
|
-
x.report("#{suite[:name]} (1 thread)") do
|
|
62
|
-
with_retries { suite[:block].call }
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Multi Threaded (4 threads)
|
|
66
|
-
x.report("#{suite[:name]} (4 threads)") do
|
|
67
|
-
threads = 4.times.map do
|
|
68
|
-
Thread.new do
|
|
69
|
-
# Ensure each thread gets a dedicated connection
|
|
70
|
-
ActiveRecord::Base.connection_pool.with_connection do
|
|
71
|
-
with_retries { suite[:block].call }
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
threads.each(&:join)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
x.compare!
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Capture memory after
|
|
82
|
-
mem_after = GetProcessMem.new.mb
|
|
83
|
-
|
|
84
|
-
results[suite[:name]] = {
|
|
85
|
-
report: report,
|
|
86
|
-
memory_delta_mb: mem_after - mem_before,
|
|
87
|
-
weight: suite[:weight]
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
puts "Memory Footprint: #{mem_after.round(2)} MB (+#{(mem_after - mem_before).round(2)} MB)" unless @json_output
|
|
91
|
-
puts "\n" unless @json_output
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
print_summary(results)
|
|
95
|
-
results
|
|
18
|
+
def initialize(config)
|
|
19
|
+
@config = config
|
|
96
20
|
end
|
|
97
21
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
rescue ActiveRecord::StatementInvalid => e
|
|
103
|
-
if e.message =~ /locked/i
|
|
104
|
-
# Specifically drop the lock for THIS connection only
|
|
105
|
-
ActiveRecord::Base.connection.reset!
|
|
106
|
-
sleep(rand(0.01..0.05))
|
|
107
|
-
retry
|
|
108
|
-
else
|
|
109
|
-
raise e
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def system_report
|
|
114
|
-
info = RailsBenchmarkSuite::Reporter.system_info
|
|
115
|
-
yjit_status = if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
|
|
116
|
-
"Enabled"
|
|
117
|
-
else
|
|
118
|
-
"Disabled (Requires Ruby with YJIT support for best results)"
|
|
119
|
-
end
|
|
120
|
-
"System: Ruby #{info[:ruby_version]} (#{info[:platform]}), #{info[:processors]} Cores. YJIT: #{yjit_status}. Libvips: #{info[:libvips]}"
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def print_summary(results)
|
|
124
|
-
if @json_output
|
|
125
|
-
print_json(results)
|
|
126
|
-
return
|
|
22
|
+
def run
|
|
23
|
+
# Load workloads dynamically if not already loaded (idempotent)
|
|
24
|
+
if Runner.instance_variable_get(:@workloads).empty?
|
|
25
|
+
Dir[File.join(__dir__, "workloads", "*.rb")].each { |f| require f }
|
|
127
26
|
end
|
|
128
27
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
puts "| %-25s | %-25s | %-12s | %-15s |" % ["Suite", "IPS (1t / 4t)", "Scaling", "Mem Delta"]
|
|
132
|
-
puts "=========================================================================================="
|
|
28
|
+
# 1. Setup Database
|
|
29
|
+
DatabaseManager.new.setup(use_local_db: @config.db)
|
|
133
30
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
entry_1t = entries.find { |e| e.label.include?("(1 thread)") }
|
|
141
|
-
entry_4t = entries.find { |e| e.label.include?("(4 threads)") }
|
|
142
|
-
|
|
143
|
-
ips_1t = entry_1t ? entry_1t.ips : 0
|
|
144
|
-
ips_4t = entry_4t ? entry_4t.ips : 0
|
|
145
|
-
|
|
146
|
-
scaling = ips_1t > 0 ? (ips_4t / ips_1t) : 0
|
|
147
|
-
mem = data[:memory_delta_mb]
|
|
148
|
-
|
|
149
|
-
# Heft Score: Weighted Sum of 4t IPS
|
|
150
|
-
weight = data[:weight] || 1.0
|
|
151
|
-
weighted_score = ips_4t * weight
|
|
152
|
-
total_score += weighted_score
|
|
153
|
-
|
|
154
|
-
puts "| %-25s | %-25s | x%-11.2f | +%-14.2fMB |" % [
|
|
155
|
-
name + " (w: #{weight})",
|
|
156
|
-
"#{humanize(ips_1t)} / #{humanize(ips_4t)}",
|
|
157
|
-
scaling,
|
|
158
|
-
mem
|
|
159
|
-
]
|
|
160
|
-
end
|
|
161
|
-
puts "=========================================================================================="
|
|
162
|
-
puts "\n"
|
|
163
|
-
puts " >>> FINAL HEFT SCORE: #{total_score.round(0)} <<<"
|
|
164
|
-
puts "\n"
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def print_json(results)
|
|
168
|
-
require "json"
|
|
31
|
+
# 2. Display Header
|
|
32
|
+
header_info = Reporter.system_info.merge(
|
|
33
|
+
threads: @config.threads,
|
|
34
|
+
db_mode: @config.db_mode
|
|
35
|
+
)
|
|
36
|
+
Reporter.header(header_info) unless @config.json
|
|
169
37
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
38
|
+
# 3. Execute Workloads
|
|
39
|
+
# Passing config values as options to WorkloadRunner for compatibility
|
|
40
|
+
# Ideally we'd pass the config object but WorkloadRunner expects a hash currently
|
|
41
|
+
# We will refactor WorkloadRunner to accept config later or wrap it here
|
|
42
|
+
runner_options = {
|
|
43
|
+
threads: @config.threads,
|
|
44
|
+
profile: @config.profile
|
|
174
45
|
}
|
|
175
46
|
|
|
176
|
-
|
|
47
|
+
payload = WorkloadRunner.new(
|
|
48
|
+
Runner.instance_variable_get(:@workloads),
|
|
49
|
+
options: runner_options,
|
|
50
|
+
show_progress: !@config.json
|
|
51
|
+
).execute
|
|
177
52
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
ips_4t = data[:report].entries.find { |e| e.label.include?("(4 threads)") }&.ips || 0
|
|
184
|
-
|
|
185
|
-
weighted_score = ips_4t * weight
|
|
186
|
-
total_score += weighted_score
|
|
187
|
-
|
|
188
|
-
out[:suites] << {
|
|
189
|
-
name: name,
|
|
190
|
-
weight: weight,
|
|
191
|
-
ips_1t: ips_1t,
|
|
192
|
-
ips_4t: ips_4t,
|
|
193
|
-
scaling: ips_1t > 0 ? (ips_4t / ips_1t) : 0,
|
|
194
|
-
memory_delta_mb: data[:memory_delta_mb],
|
|
195
|
-
score: weighted_score
|
|
196
|
-
}
|
|
53
|
+
# 4. Report Results
|
|
54
|
+
if @config.json
|
|
55
|
+
Reporter.as_json(payload)
|
|
56
|
+
else
|
|
57
|
+
Reporter.render(payload)
|
|
197
58
|
end
|
|
198
|
-
|
|
199
|
-
out[:total_score] = total_score.round(0)
|
|
200
|
-
puts out.to_json
|
|
201
|
-
end
|
|
202
59
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
else
|
|
208
|
-
"%.1f" % ips
|
|
60
|
+
# 5. HTML Report Generation
|
|
61
|
+
if @config.html
|
|
62
|
+
require_relative "reporters/html_reporter"
|
|
63
|
+
Reporters::HtmlReporter.new(payload).generate
|
|
209
64
|
end
|
|
210
65
|
end
|
|
211
66
|
end
|
|
@@ -6,16 +6,16 @@ module RailsBenchmarkSuite
|
|
|
6
6
|
module Schema
|
|
7
7
|
def self.load
|
|
8
8
|
ActiveRecord::Schema.define do
|
|
9
|
-
#
|
|
10
|
-
create_table :
|
|
9
|
+
# BenchmarkUsers
|
|
10
|
+
create_table :benchmark_users, force: true do |t|
|
|
11
11
|
t.string :name
|
|
12
12
|
t.string :email
|
|
13
13
|
t.timestamps
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
create_table :
|
|
18
|
-
t.references :
|
|
16
|
+
# BenchmarkPosts
|
|
17
|
+
create_table :benchmark_posts, force: true do |t|
|
|
18
|
+
t.references :benchmark_user, foreign_key: { to_table: :benchmark_users }
|
|
19
19
|
t.string :title
|
|
20
20
|
t.text :body
|
|
21
21
|
t.integer :views, default: 0
|