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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +0 -20
  3. data/Gemfile +1 -1
  4. data/lib/serialbench/benchmark_runner.rb +12 -297
  5. data/lib/serialbench/cli.rb +0 -5
  6. data/lib/serialbench/models/result_store.rb +0 -38
  7. data/lib/serialbench/models.rb +0 -5
  8. data/lib/serialbench/serializers/base_serializer.rb +0 -4
  9. data/lib/serialbench/serializers/json/base_json_serializer.rb +8 -20
  10. data/lib/serialbench/serializers/json/json_serializer.rb +0 -16
  11. data/lib/serialbench/serializers/json/oj_serializer.rb +2 -17
  12. data/lib/serialbench/serializers/json/yajl_serializer.rb +2 -2
  13. data/lib/serialbench/serializers/toml/base_toml_serializer.rb +8 -29
  14. data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +0 -16
  15. data/lib/serialbench/serializers/toml/tomlib_serializer.rb +0 -4
  16. data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +2 -18
  17. data/lib/serialbench/serializers/xml/base_xml_serializer.rb +4 -16
  18. data/lib/serialbench/serializers/xml/leptris_serializer.rb +1 -5
  19. data/lib/serialbench/serializers/xml/libxml_serializer.rb +4 -9
  20. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +3 -3
  21. data/lib/serialbench/serializers/xml/oga_serializer.rb +3 -5
  22. data/lib/serialbench/serializers/xml/ox_serializer.rb +2 -2
  23. data/lib/serialbench/serializers/xml/rexml_serializer.rb +5 -12
  24. data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +1 -9
  25. data/lib/serialbench/serializers/yaml/syck_serializer.rb +0 -8
  26. data/lib/serialbench/test_data.rb +315 -0
  27. data/lib/serialbench/version.rb +1 -1
  28. data/lib/serialbench.rb +2 -1
  29. data/serialbench.gemspec +1 -2
  30. metadata +6 -22
  31. data/lib/serialbench/cli/resultset_cli.rb +0 -301
  32. data/lib/serialbench/models/result_set.rb +0 -79
  33. 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