serialbench 0.3.1 → 0.4.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 +4 -4
- data/.github/workflows/benchmark.yml +0 -20
- data/Gemfile +1 -1
- data/lib/serialbench/benchmark_runner.rb +12 -297
- data/lib/serialbench/cli.rb +0 -5
- data/lib/serialbench/models/result_store.rb +0 -38
- data/lib/serialbench/models.rb +0 -5
- data/lib/serialbench/serializers/base_serializer.rb +0 -4
- data/lib/serialbench/serializers/json/base_json_serializer.rb +8 -20
- data/lib/serialbench/serializers/json/json_serializer.rb +0 -16
- data/lib/serialbench/serializers/json/oj_serializer.rb +2 -17
- data/lib/serialbench/serializers/json/yajl_serializer.rb +2 -2
- data/lib/serialbench/serializers/toml/base_toml_serializer.rb +8 -29
- data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +0 -16
- data/lib/serialbench/serializers/toml/tomlib_serializer.rb +0 -4
- data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +2 -18
- data/lib/serialbench/serializers/xml/base_xml_serializer.rb +4 -16
- data/lib/serialbench/serializers/xml/leptris_serializer.rb +1 -5
- data/lib/serialbench/serializers/xml/libxml_serializer.rb +4 -9
- data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +3 -3
- data/lib/serialbench/serializers/xml/oga_serializer.rb +3 -5
- data/lib/serialbench/serializers/xml/ox_serializer.rb +2 -2
- data/lib/serialbench/serializers/xml/rexml_serializer.rb +5 -12
- data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +1 -9
- data/lib/serialbench/serializers/yaml/syck_serializer.rb +0 -8
- data/lib/serialbench/test_data.rb +315 -0
- data/lib/serialbench/version.rb +1 -1
- data/lib/serialbench.rb +2 -1
- data/serialbench.gemspec +1 -2
- metadata +6 -22
- data/lib/serialbench/cli/resultset_cli.rb +0 -301
- data/lib/serialbench/models/result_set.rb +0 -79
- data/lib/serialbench/site_generator.rb +0 -337
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'fileutils'
|
|
4
|
-
require 'erb'
|
|
5
|
-
require 'json'
|
|
6
|
-
require 'liquid'
|
|
7
|
-
require 'yaml'
|
|
8
|
-
|
|
9
|
-
module Serialbench
|
|
10
|
-
# Unified site generator for creating static HTML sites from benchmark results
|
|
11
|
-
class SiteGenerator
|
|
12
|
-
TEMPLATE_DIR = File.join(__dir__, 'templates')
|
|
13
|
-
|
|
14
|
-
attr_reader :output_path, :result, :resultset
|
|
15
|
-
|
|
16
|
-
def initialize(output_path:, result: nil, resultset: nil)
|
|
17
|
-
@output_path = File.expand_path(output_path)
|
|
18
|
-
@result = result if result
|
|
19
|
-
@resultset = resultset if resultset
|
|
20
|
-
setup_liquid_environment
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.generate_for_result(result, output_path)
|
|
24
|
-
generator = new(output_path: output_path, result: result)
|
|
25
|
-
generator.generate_site
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def self.generate_for_resultset(resultset, output_path)
|
|
29
|
-
generator = new(output_path: output_path, resultset: resultset)
|
|
30
|
-
generator.generate_site
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def self.resultset_payload(resultset)
|
|
34
|
-
new(output_path: '/dev/null', resultset: resultset).resultset_payload
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def self.export_raw_data(resultset, data_dir)
|
|
38
|
-
new(output_path: '/dev/null', resultset: resultset).export_raw_data_to(data_dir)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def resultset_payload
|
|
42
|
-
transform_resultset_for_dashboard(@resultset)
|
|
43
|
-
.merge('libraries' => libraries_information(@resultset))
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Static capability matrix (adapter declarations) plus the versions
|
|
47
|
-
# actually measured in this resultset.
|
|
48
|
-
def libraries_information(resultset)
|
|
49
|
-
versions = {}
|
|
50
|
-
resultset.results.each do |result|
|
|
51
|
-
serializers = result.benchmark_result.serializers || []
|
|
52
|
-
serializers.each { |si| versions[si.name] ||= si.version }
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
Serialbench::Serializers.all.filter_map do |adapter|
|
|
56
|
-
next if adapter.name == 'syck'
|
|
57
|
-
|
|
58
|
-
features = adapter.features
|
|
59
|
-
next if features.is_a?(Array)
|
|
60
|
-
|
|
61
|
-
{
|
|
62
|
-
'name' => adapter.name,
|
|
63
|
-
'format' => adapter.format.to_s,
|
|
64
|
-
'version' => versions[adapter.name] || 'unknown',
|
|
65
|
-
'features' => features.transform_values { |v| v == true }
|
|
66
|
-
}
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def export_raw_data_to(data_dir)
|
|
71
|
-
FileUtils.mkdir_p(data_dir)
|
|
72
|
-
export_resultset(@resultset, data_dir)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def generate_site
|
|
76
|
-
target_name = @result ? @result.environment_config.name : @resultset.name
|
|
77
|
-
|
|
78
|
-
puts "🏗️ Generating HTML site for #{@result ? 'run' : 'resultset'}: #{target_name}"
|
|
79
|
-
puts "Output: #{@output_path}"
|
|
80
|
-
|
|
81
|
-
# Transform data for dashboard.js compatibility
|
|
82
|
-
data = if @result
|
|
83
|
-
transform_result_for_dashboard(@result)
|
|
84
|
-
else
|
|
85
|
-
transform_resultset_for_dashboard(@resultset)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
prepare_output_directory
|
|
89
|
-
render_site(
|
|
90
|
-
{
|
|
91
|
-
'data' => JSON.generate(data),
|
|
92
|
-
'kind' => @result ? 'run' : 'resultset'
|
|
93
|
-
},
|
|
94
|
-
'format_based.liquid'
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
# Export raw data files for download
|
|
98
|
-
export_raw_data
|
|
99
|
-
|
|
100
|
-
puts "✅ Site generated successfully at: #{@output_path}"
|
|
101
|
-
@output_path
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
private
|
|
105
|
-
|
|
106
|
-
def setup_liquid_environment
|
|
107
|
-
@liquid_env = Liquid::Environment.new
|
|
108
|
-
@liquid_env.file_system = Liquid::LocalFileSystem.new(TEMPLATE_DIR)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def prepare_output_directory
|
|
112
|
-
if Dir.exist?(@output_path)
|
|
113
|
-
puts 'Cleaning existing output directory...'
|
|
114
|
-
FileUtils.rm_rf(Dir.glob(File.join(@output_path, '*')))
|
|
115
|
-
else
|
|
116
|
-
FileUtils.mkdir_p(@output_path)
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def render_site(template_data, template_name)
|
|
121
|
-
# Load and render content template
|
|
122
|
-
content_template = load_template(template_name)
|
|
123
|
-
content = content_template.render(template_data)
|
|
124
|
-
|
|
125
|
-
# Load and render base template
|
|
126
|
-
base_template = load_template('base.liquid')
|
|
127
|
-
html = base_template.render(template_data.merge('content' => content))
|
|
128
|
-
|
|
129
|
-
# Write HTML file
|
|
130
|
-
write_file(html, 'index.html')
|
|
131
|
-
|
|
132
|
-
# Copy assets
|
|
133
|
-
copy_assets
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def load_template(template_name)
|
|
137
|
-
template_path = File.join(TEMPLATE_DIR, template_name)
|
|
138
|
-
template_content = File.read(template_path)
|
|
139
|
-
Liquid::Template.parse(template_content)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def write_file(content, filename)
|
|
143
|
-
FileUtils.mkdir_p(@output_path)
|
|
144
|
-
File.write(File.join(@output_path, filename), content)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def copy_assets
|
|
148
|
-
assets_source = File.join(TEMPLATE_DIR, 'assets')
|
|
149
|
-
assets_dest = File.join(@output_path, 'assets')
|
|
150
|
-
|
|
151
|
-
return unless Dir.exist?(assets_source)
|
|
152
|
-
|
|
153
|
-
FileUtils.cp_r(assets_source, assets_dest)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# Transform a single Result into dashboard-compatible format
|
|
157
|
-
# Dashboard expects: { combined_results: {...}, environments: {...}, metadata: {...} }
|
|
158
|
-
def transform_result_for_dashboard(result)
|
|
159
|
-
env_key = "env-#{result.platform.ruby_version}"
|
|
160
|
-
|
|
161
|
-
# Build combined_results structure
|
|
162
|
-
combined_results = build_combined_results(result, env_key)
|
|
163
|
-
|
|
164
|
-
# Build environments structure
|
|
165
|
-
environments = {
|
|
166
|
-
env_key => {
|
|
167
|
-
'ruby_version' => result.platform.ruby_version,
|
|
168
|
-
'ruby_platform' => result.platform.ruby_platform || "#{result.platform.os}-#{result.platform.arch}",
|
|
169
|
-
'os' => result.platform.os,
|
|
170
|
-
'arch' => result.platform.arch,
|
|
171
|
-
'source_file' => result.metadata.environment_config_path,
|
|
172
|
-
'timestamp' => result.metadata.created_at
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
{
|
|
177
|
-
'combined_results' => combined_results,
|
|
178
|
-
'environments' => environments,
|
|
179
|
-
'metadata' => {
|
|
180
|
-
'generated_at' => Time.now.iso8601
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
# Environment identity for the site: use the CI platform_string
|
|
186
|
-
# ("macos-26-ruby-3.4") when it is runner-shaped, so each runner label is
|
|
187
|
-
# its own environment; fall back to os-arch for other sources (docker,
|
|
188
|
-
# local runs, older resultsets).
|
|
189
|
-
def env_identity(result)
|
|
190
|
-
platform_string = result.platform.platform_string
|
|
191
|
-
ruby = result.platform.ruby_version
|
|
192
|
-
return platform_string if platform_string&.end_with?("-ruby-#{ruby}")
|
|
193
|
-
|
|
194
|
-
"#{result.platform.os}-#{result.platform.arch}-ruby-#{ruby}"
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# Transform a ResultSet (collection of Results) into dashboard-compatible format
|
|
198
|
-
# Combines all results into a single dashboard structure
|
|
199
|
-
def transform_resultset_for_dashboard(resultset)
|
|
200
|
-
combined_results = {}
|
|
201
|
-
environments = {}
|
|
202
|
-
|
|
203
|
-
resultset.results.each do |result|
|
|
204
|
-
# Create unique env key for this result
|
|
205
|
-
env_key = env_identity(result)
|
|
206
|
-
|
|
207
|
-
# Merge this result's data into combined_results
|
|
208
|
-
result_combined = build_combined_results(result, env_key)
|
|
209
|
-
merge_combined_results!(combined_results, result_combined)
|
|
210
|
-
|
|
211
|
-
# Add environment info
|
|
212
|
-
environments[env_key] = {
|
|
213
|
-
'ruby_version' => result.platform.ruby_version,
|
|
214
|
-
'ruby_platform' => result.platform.ruby_platform || "#{result.platform.os}-#{result.platform.arch}",
|
|
215
|
-
'os' => result.platform.os,
|
|
216
|
-
'arch' => result.platform.arch,
|
|
217
|
-
'source_file' => result.metadata.environment_config_path,
|
|
218
|
-
'timestamp' => result.metadata.created_at
|
|
219
|
-
}
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
{
|
|
223
|
-
'combined_results' => combined_results,
|
|
224
|
-
'environments' => environments,
|
|
225
|
-
'metadata' => {
|
|
226
|
-
'resultset_name' => resultset.name,
|
|
227
|
-
'resultset_description' => resultset.description,
|
|
228
|
-
'total_runs' => resultset.results.size,
|
|
229
|
-
'generated_at' => Time.now.iso8601
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
# Deep merge results from multiple runs
|
|
235
|
-
def merge_combined_results!(target, source)
|
|
236
|
-
source.each do |operation, sizes|
|
|
237
|
-
target[operation] ||= {}
|
|
238
|
-
sizes.each do |size, formats|
|
|
239
|
-
target[operation][size] ||= {}
|
|
240
|
-
formats.each do |format, serializers|
|
|
241
|
-
target[operation][size][format] ||= {}
|
|
242
|
-
serializers.each do |serializer, envs|
|
|
243
|
-
target[operation][size][format][serializer] ||= {}
|
|
244
|
-
target[operation][size][format][serializer].merge!(envs)
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
def build_combined_results(result, env_key)
|
|
252
|
-
combined = {}
|
|
253
|
-
|
|
254
|
-
%w[parsing generation streaming].each do |operation|
|
|
255
|
-
combined[operation] = {}
|
|
256
|
-
|
|
257
|
-
operation_results = result.benchmark_result.send(operation)
|
|
258
|
-
next if operation_results.nil? || operation_results.empty?
|
|
259
|
-
|
|
260
|
-
operation_results.each do |perf|
|
|
261
|
-
size = perf.data_size
|
|
262
|
-
format = perf.format
|
|
263
|
-
serializer = perf.adapter
|
|
264
|
-
|
|
265
|
-
combined[operation][size] ||= {}
|
|
266
|
-
combined[operation][size][format] ||= {}
|
|
267
|
-
combined[operation][size][format][serializer] ||= {}
|
|
268
|
-
combined[operation][size][format][serializer][env_key] = {
|
|
269
|
-
'iterations_per_second' => perf.iterations_per_second,
|
|
270
|
-
'time_per_iteration' => perf.time_per_iteration
|
|
271
|
-
}
|
|
272
|
-
end
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
# Handle memory separately
|
|
276
|
-
if result.benchmark_result.memory && !result.benchmark_result.memory.empty?
|
|
277
|
-
combined['memory'] = {}
|
|
278
|
-
|
|
279
|
-
result.benchmark_result.memory.each do |mem|
|
|
280
|
-
size = mem.data_size
|
|
281
|
-
format = mem.format
|
|
282
|
-
serializer = mem.adapter
|
|
283
|
-
|
|
284
|
-
combined['memory'][size] ||= {}
|
|
285
|
-
combined['memory'][size][format] ||= {}
|
|
286
|
-
combined['memory'][size][format][serializer] ||= {}
|
|
287
|
-
combined['memory'][size][format][serializer][env_key] = {
|
|
288
|
-
'allocated_memory' => mem.allocated_memory,
|
|
289
|
-
'retained_memory' => mem.retained_memory
|
|
290
|
-
}
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
combined
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
# Export raw data files for download
|
|
298
|
-
def export_raw_data
|
|
299
|
-
data_dir = File.join(@output_path, 'data')
|
|
300
|
-
FileUtils.mkdir_p(data_dir)
|
|
301
|
-
|
|
302
|
-
if @result
|
|
303
|
-
# Export single result
|
|
304
|
-
export_single_result(@result, data_dir)
|
|
305
|
-
elsif @resultset
|
|
306
|
-
# Export all results in resultset plus the complete resultset
|
|
307
|
-
export_resultset(@resultset, data_dir)
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
puts "📦 Raw data files exported to: #{data_dir}"
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
def export_single_result(result, data_dir)
|
|
314
|
-
# Create filename based on platform info
|
|
315
|
-
env_key = env_identity(result)
|
|
316
|
-
filename = "#{env_key}.yaml"
|
|
317
|
-
filepath = File.join(data_dir, filename)
|
|
318
|
-
|
|
319
|
-
# Write result as YAML
|
|
320
|
-
File.write(filepath, result.to_yaml)
|
|
321
|
-
puts " 📄 Exported: #{filename}"
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
def export_resultset(resultset, data_dir)
|
|
325
|
-
# Export each individual result
|
|
326
|
-
resultset.results.each do |result|
|
|
327
|
-
export_single_result(result, data_dir)
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
# Export complete resultset
|
|
331
|
-
resultset_filename = 'resultset.yaml'
|
|
332
|
-
resultset_filepath = File.join(data_dir, resultset_filename)
|
|
333
|
-
File.write(resultset_filepath, resultset.to_yaml)
|
|
334
|
-
puts " 📄 Exported: #{resultset_filename} (complete dataset)"
|
|
335
|
-
end
|
|
336
|
-
end
|
|
337
|
-
end
|