serialbench 0.1.0 → 0.1.2
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 +181 -30
- data/.github/workflows/ci.yml +3 -3
- data/.github/workflows/docker.yml +272 -0
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +25 -0
- data/Gemfile +6 -30
- data/README.adoc +381 -415
- data/Rakefile +0 -55
- data/config/benchmarks/full.yml +29 -0
- data/config/benchmarks/short.yml +26 -0
- data/config/environments/asdf-ruby-3.2.yml +8 -0
- data/config/environments/asdf-ruby-3.3.yml +8 -0
- data/config/environments/docker-ruby-3.0.yml +9 -0
- data/config/environments/docker-ruby-3.1.yml +9 -0
- data/config/environments/docker-ruby-3.2.yml +9 -0
- data/config/environments/docker-ruby-3.3.yml +9 -0
- data/config/environments/docker-ruby-3.4.yml +9 -0
- data/docker/Dockerfile.alpine +33 -0
- data/docker/Dockerfile.ubuntu +32 -0
- data/docker/README.md +214 -0
- data/exe/serialbench +1 -1
- data/lib/serialbench/benchmark_runner.rb +270 -350
- data/lib/serialbench/cli/base_cli.rb +51 -0
- data/lib/serialbench/cli/benchmark_cli.rb +380 -0
- data/lib/serialbench/cli/environment_cli.rb +181 -0
- data/lib/serialbench/cli/resultset_cli.rb +215 -0
- data/lib/serialbench/cli/ruby_build_cli.rb +238 -0
- data/lib/serialbench/cli.rb +59 -410
- data/lib/serialbench/config_manager.rb +140 -0
- data/lib/serialbench/models/benchmark_config.rb +63 -0
- data/lib/serialbench/models/benchmark_result.rb +45 -0
- data/lib/serialbench/models/environment_config.rb +71 -0
- data/lib/serialbench/models/platform.rb +59 -0
- data/lib/serialbench/models/result.rb +53 -0
- data/lib/serialbench/models/result_set.rb +71 -0
- data/lib/serialbench/models/result_store.rb +108 -0
- data/lib/serialbench/models.rb +54 -0
- data/lib/serialbench/ruby_build_manager.rb +153 -0
- data/lib/serialbench/runners/asdf_runner.rb +296 -0
- data/lib/serialbench/runners/base.rb +32 -0
- data/lib/serialbench/runners/docker_runner.rb +142 -0
- data/lib/serialbench/serializers/base_serializer.rb +8 -16
- data/lib/serialbench/serializers/json/base_json_serializer.rb +4 -4
- data/lib/serialbench/serializers/json/json_serializer.rb +0 -2
- data/lib/serialbench/serializers/json/oj_serializer.rb +0 -2
- data/lib/serialbench/serializers/json/rapidjson_serializer.rb +50 -0
- data/lib/serialbench/serializers/json/yajl_serializer.rb +6 -4
- data/lib/serialbench/serializers/toml/base_toml_serializer.rb +5 -3
- data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +0 -2
- data/lib/serialbench/serializers/toml/tomlib_serializer.rb +0 -2
- data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +56 -0
- data/lib/serialbench/serializers/xml/base_xml_serializer.rb +4 -9
- data/lib/serialbench/serializers/xml/libxml_serializer.rb +0 -2
- data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +21 -5
- data/lib/serialbench/serializers/xml/oga_serializer.rb +0 -2
- data/lib/serialbench/serializers/xml/ox_serializer.rb +0 -2
- data/lib/serialbench/serializers/xml/rexml_serializer.rb +32 -4
- data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +59 -0
- data/lib/serialbench/serializers/yaml/psych_serializer.rb +54 -0
- data/lib/serialbench/serializers/yaml/syck_serializer.rb +102 -0
- data/lib/serialbench/serializers.rb +34 -6
- data/lib/serialbench/site_generator.rb +105 -0
- data/lib/serialbench/templates/assets/css/benchmark_report.css +535 -0
- data/lib/serialbench/templates/assets/css/format_based.css +526 -0
- data/lib/serialbench/templates/assets/css/themes.css +588 -0
- data/lib/serialbench/templates/assets/js/chart_helpers.js +381 -0
- data/lib/serialbench/templates/assets/js/dashboard.js +796 -0
- data/lib/serialbench/templates/assets/js/navigation.js +142 -0
- data/lib/serialbench/templates/base.liquid +49 -0
- data/lib/serialbench/templates/format_based.liquid +279 -0
- data/lib/serialbench/templates/partials/chart_section.liquid +4 -0
- data/lib/serialbench/version.rb +1 -1
- data/lib/serialbench.rb +2 -31
- data/serialbench.gemspec +28 -17
- metadata +192 -55
- data/lib/serialbench/chart_generator.rb +0 -821
- data/lib/serialbench/result_formatter.rb +0 -182
- data/lib/serialbench/result_merger.rb +0 -1201
- data/lib/serialbench/serializers/xml/base_parser.rb +0 -69
- data/lib/serialbench/serializers/xml/libxml_parser.rb +0 -98
- data/lib/serialbench/serializers/xml/nokogiri_parser.rb +0 -111
- data/lib/serialbench/serializers/xml/oga_parser.rb +0 -85
- data/lib/serialbench/serializers/xml/ox_parser.rb +0 -64
- data/lib/serialbench/serializers/xml/rexml_parser.rb +0 -129
data/lib/serialbench/cli.rb
CHANGED
@@ -1,438 +1,87 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'thor'
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
require_relative 'cli/base_cli'
|
5
|
+
require_relative 'cli/environment_cli'
|
6
|
+
require_relative 'cli/benchmark_cli'
|
7
|
+
require_relative 'cli/resultset_cli'
|
8
|
+
require_relative 'cli/ruby_build_cli'
|
7
9
|
|
8
10
|
module Serialbench
|
9
|
-
#
|
10
|
-
class
|
11
|
-
|
11
|
+
# Main CLI entry point for the new object-oriented command structure
|
12
|
+
class CLI < Serialbench::Cli::BaseCli
|
13
|
+
desc 'environment SUBCOMMAND', 'Manage benchmark environments'
|
14
|
+
subcommand 'environment', Serialbench::Cli::EnvironmentCli
|
12
15
|
|
13
|
-
desc 'benchmark', '
|
14
|
-
|
15
|
-
Run the complete benchmark suite for all available serialization libraries.
|
16
|
+
desc 'benchmark SUBCOMMAND', 'Manage individual benchmark runs'
|
17
|
+
subcommand 'benchmark', Serialbench::Cli::BenchmarkCli
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
DESC
|
20
|
-
option :formats, type: :array, default: %w[xml json toml],
|
21
|
-
desc: 'Formats to benchmark (xml, json, toml)'
|
22
|
-
option :output_format, type: :string, default: 'all',
|
23
|
-
desc: 'Output format: all, json, yaml, html'
|
24
|
-
option :parsing_only, type: :boolean, default: false,
|
25
|
-
desc: 'Run only parsing benchmarks'
|
26
|
-
option :generation_only, type: :boolean, default: false,
|
27
|
-
desc: 'Run only generation benchmarks'
|
28
|
-
option :streaming_only, type: :boolean, default: false,
|
29
|
-
desc: 'Run only streaming benchmarks'
|
30
|
-
option :memory_only, type: :boolean, default: false,
|
31
|
-
desc: 'Run only memory usage benchmarks'
|
32
|
-
option :iterations, type: :numeric, default: 10,
|
33
|
-
desc: 'Number of benchmark iterations'
|
34
|
-
option :warmup, type: :numeric, default: 3,
|
35
|
-
desc: 'Number of warmup iterations'
|
36
|
-
def benchmark
|
37
|
-
say 'SerialBench - Comprehensive Serialization Performance Tests', :green
|
38
|
-
say '=' * 70, :green
|
19
|
+
desc 'resultset SUBCOMMAND', 'Manage benchmark resultsets (collections of runs)'
|
20
|
+
subcommand 'resultset', Serialbench::Cli::ResultsetCli
|
39
21
|
|
40
|
-
|
41
|
-
|
42
|
-
invalid_formats = options[:formats] - valid_formats
|
43
|
-
unless invalid_formats.empty?
|
44
|
-
say "Invalid formats: #{invalid_formats.join(', ')}", :red
|
45
|
-
say "Valid formats: #{valid_formats.join(', ')}", :yellow
|
46
|
-
exit 1
|
47
|
-
end
|
48
|
-
|
49
|
-
# Convert format strings to symbols
|
50
|
-
formats = options[:formats].map(&:to_sym)
|
51
|
-
|
52
|
-
# Show available serializers
|
53
|
-
show_available_serializers(formats)
|
54
|
-
|
55
|
-
# Run benchmarks
|
56
|
-
runner_options = {
|
57
|
-
formats: formats,
|
58
|
-
iterations: options[:iterations],
|
59
|
-
warmup: options[:warmup]
|
60
|
-
}
|
22
|
+
desc 'ruby_build SUBCOMMAND', 'Manage Ruby-Build definitions for validation'
|
23
|
+
subcommand 'ruby_build', Serialbench::Cli::RubyBuildCli
|
61
24
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
results = run_selected_benchmarks(runner)
|
66
|
-
save_results(results)
|
67
|
-
show_summary(results) unless %w[json yaml].include?(options[:output_format])
|
68
|
-
rescue StandardError => e
|
69
|
-
say "Error running benchmarks: #{e.message}", :red
|
70
|
-
say e.backtrace.first(5).join("\n"), :red if ENV['DEBUG']
|
71
|
-
exit 1
|
72
|
-
end
|
25
|
+
desc 'version', 'Show Serialbench version'
|
26
|
+
def self.version
|
27
|
+
puts "Serialbench version #{Serialbench::VERSION}"
|
73
28
|
end
|
74
29
|
|
75
|
-
desc '
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
Shows which libraries are installed and available for benchmarking,
|
80
|
-
along with their versions.
|
81
|
-
DESC
|
82
|
-
option :format, type: :string, desc: 'Show only serializers for specific format'
|
83
|
-
def list
|
84
|
-
say 'Available Serializers', :green
|
85
|
-
say '=' * 30, :green
|
86
|
-
|
87
|
-
if options[:format]
|
88
|
-
format_sym = options[:format].to_sym
|
89
|
-
serializers = Serialbench::Serializers.available_for_format(format_sym)
|
90
|
-
|
91
|
-
if serializers.empty?
|
92
|
-
say "No available serializers for format: #{options[:format]}", :yellow
|
93
|
-
else
|
94
|
-
show_serializers_for_format(format_sym, serializers)
|
95
|
-
end
|
30
|
+
desc 'help [COMMAND]', 'Show help for commands'
|
31
|
+
def help(command = nil)
|
32
|
+
if command
|
33
|
+
super(command)
|
96
34
|
else
|
97
|
-
|
98
|
-
|
99
|
-
next if serializers.empty?
|
100
|
-
|
101
|
-
show_serializers_for_format(format, serializers)
|
102
|
-
say ''
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
desc 'version', 'Show SerialBench version'
|
108
|
-
def version
|
109
|
-
say "SerialBench version #{Serialbench::VERSION}", :green
|
110
|
-
end
|
111
|
-
|
112
|
-
desc 'merge_results INPUT_DIRS... OUTPUT_DIR', 'Merge benchmark results from multiple runs'
|
113
|
-
long_desc <<~DESC
|
114
|
-
Merge benchmark results from multiple Ruby versions or different environments.
|
115
|
-
|
116
|
-
INPUT_DIRS should contain results.json files from different benchmark runs.
|
117
|
-
OUTPUT_DIR will contain the merged results and comparative reports.
|
118
|
-
|
119
|
-
Example:
|
120
|
-
serialbench merge_results ruby-3.0/results ruby-3.1/results ruby-3.2/results merged_output/
|
121
|
-
DESC
|
122
|
-
def merge_results(*args)
|
123
|
-
if args.length < 2
|
124
|
-
say 'Error: Need at least one input directory and one output directory', :red
|
125
|
-
say 'Usage: serialbench merge_results INPUT_DIRS... OUTPUT_DIR', :yellow
|
126
|
-
exit 1
|
127
|
-
end
|
128
|
-
|
129
|
-
output_dir = args.pop
|
130
|
-
input_dirs = args
|
131
|
-
|
132
|
-
say "Merging benchmark results from #{input_dirs.length} directories to #{output_dir}", :green
|
133
|
-
|
134
|
-
begin
|
135
|
-
merger = Serialbench::ResultMerger.new
|
136
|
-
merged_file = merger.merge_directories(input_dirs, output_dir)
|
137
|
-
say "Results merged successfully to: #{merged_file}", :green
|
138
|
-
rescue StandardError => e
|
139
|
-
say "Error merging results: #{e.message}", :red
|
140
|
-
exit 1
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
desc 'github_pages INPUT_DIRS... OUTPUT_DIR', 'Generate GitHub Pages HTML from multiple benchmark runs'
|
145
|
-
long_desc <<~DESC
|
146
|
-
Merge benchmark results from multiple Ruby versions and generate a GitHub Pages compatible HTML report.
|
147
|
-
|
148
|
-
INPUT_DIRS should contain results.json files from different benchmark runs.
|
149
|
-
OUTPUT_DIR will contain index.html and styles.css ready for GitHub Pages deployment.
|
35
|
+
puts <<~HELP
|
36
|
+
Serialbench - Benchmarking Framework for Ruby Serialization Libraries
|
150
37
|
|
151
|
-
|
38
|
+
USAGE:
|
39
|
+
serialbench COMMAND [SUBCOMMAND] [OPTIONS]
|
152
40
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
exit 1
|
161
|
-
end
|
41
|
+
COMMANDS:
|
42
|
+
environment Manage benchmark environments (Docker, ASDF, Local)
|
43
|
+
benchmark Manage individual benchmark runs
|
44
|
+
resultset Manage benchmark resultsets (collections of runs)
|
45
|
+
ruby-build Manage Ruby-Build definitions for validation
|
46
|
+
version Show version information
|
47
|
+
help Show this help message
|
162
48
|
|
163
|
-
|
164
|
-
|
49
|
+
EXAMPLES:
|
50
|
+
# Create a Docker environment
|
51
|
+
serialbench environment new docker-test docker
|
165
52
|
|
166
|
-
|
53
|
+
# Run multi-environment benchmarks
|
54
|
+
serialbench environment multi-execute asdf --config=serialbench-asdf.yml
|
55
|
+
serialbench environment multi-execute docker --config=serialbench-docker.yml
|
167
56
|
|
168
|
-
|
169
|
-
|
57
|
+
# Create and execute a benchmark
|
58
|
+
serialbench benchmark create my-benchmark
|
59
|
+
serialbench benchmark execute my-benchmark.yml
|
170
60
|
|
171
|
-
|
172
|
-
|
173
|
-
|
61
|
+
# Create a result set for comparison
|
62
|
+
serialbench resultset create comparison-set
|
63
|
+
serialbench resultset add-result comparison-set results/my-benchmark
|
174
64
|
|
175
|
-
|
176
|
-
|
177
|
-
|
65
|
+
# Generate static sites
|
66
|
+
serialbench benchmark build-site results/my-benchmark
|
67
|
+
serialbench resultset build-site resultsets/comparison-set
|
178
68
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
say " CSS: #{files[:css]}", :white
|
183
|
-
say '', :white
|
184
|
-
say 'To deploy to GitHub Pages:', :cyan
|
185
|
-
say '1. Commit and push the generated files to your repository', :white
|
186
|
-
say '2. Enable GitHub Pages in repository settings', :white
|
187
|
-
say '3. Set source to the branch containing these files', :white
|
188
|
-
rescue StandardError => e
|
189
|
-
say "Error generating GitHub Pages: #{e.message}", :red
|
190
|
-
exit 1
|
69
|
+
For detailed help on any command, use:
|
70
|
+
serialbench COMMAND help
|
71
|
+
HELP
|
191
72
|
end
|
192
73
|
end
|
193
74
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
def generate_reports(data_file)
|
201
|
-
say "Generating reports from data in #{data_file}", :green
|
202
|
-
|
203
|
-
unless File.exist?(data_file)
|
204
|
-
say "Data file does not exist: #{data_file}", :red
|
205
|
-
exit 1
|
206
|
-
end
|
207
|
-
|
208
|
-
begin
|
209
|
-
Serialbench.generate_reports_from_data(data_file)
|
210
|
-
say 'Reports generated successfully!', :green
|
211
|
-
rescue StandardError => e
|
212
|
-
say "Error generating reports: #{e.message}", :red
|
213
|
-
exit 1
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
private
|
218
|
-
|
219
|
-
def show_available_serializers(formats)
|
220
|
-
say "\nAvailable serializers:", :cyan
|
221
|
-
|
222
|
-
formats.each do |format|
|
223
|
-
serializers = Serialbench::Serializers.available_for_format(format)
|
224
|
-
next if serializers.empty?
|
225
|
-
|
226
|
-
serializer_names = serializers.map do |serializer_class|
|
227
|
-
serializer = serializer_class.new
|
228
|
-
"#{serializer.name} v#{serializer.version}"
|
229
|
-
end
|
230
|
-
|
231
|
-
say " #{format.upcase}: #{serializer_names.join(', ')}", :white
|
232
|
-
end
|
233
|
-
|
234
|
-
say "\nTest data sizes: small, medium, large", :cyan
|
235
|
-
say ''
|
236
|
-
end
|
237
|
-
|
238
|
-
def show_serializers_for_format(format, serializers)
|
239
|
-
say "#{format.upcase}:", :cyan
|
240
|
-
|
241
|
-
serializers.each do |serializer_class|
|
242
|
-
serializer = serializer_class.new
|
243
|
-
features = []
|
244
|
-
features << 'streaming' if serializer.supports_streaming?
|
245
|
-
features << 'built-in' if %w[json rexml].include?(serializer.name)
|
246
|
-
|
247
|
-
feature_text = features.empty? ? '' : " (#{features.join(', ')})"
|
248
|
-
say " ✓ #{serializer.name} v#{serializer.version}#{feature_text}", :green
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
def run_selected_benchmarks(runner)
|
253
|
-
results = { environment: runner.environment_info }
|
254
|
-
|
255
|
-
if options[:parsing_only]
|
256
|
-
say 'Running parsing benchmarks...', :yellow
|
257
|
-
results[:parsing] = runner.run_parsing_benchmarks
|
258
|
-
elsif options[:generation_only]
|
259
|
-
say 'Running generation benchmarks...', :yellow
|
260
|
-
results[:generation] = runner.run_generation_benchmarks
|
261
|
-
elsif options[:streaming_only]
|
262
|
-
say 'Running streaming benchmarks...', :yellow
|
263
|
-
results[:streaming] = runner.run_streaming_benchmarks
|
264
|
-
elsif options[:memory_only]
|
265
|
-
say 'Running memory benchmarks...', :yellow
|
266
|
-
results[:memory_usage] = runner.run_memory_benchmarks
|
267
|
-
else
|
268
|
-
say 'Running all benchmarks...', :yellow
|
269
|
-
results = runner.run_all_benchmarks
|
270
|
-
end
|
271
|
-
|
272
|
-
results
|
75
|
+
# Handle unknown commands gracefully
|
76
|
+
def method_missing(method_name, *args)
|
77
|
+
puts "Unknown command: #{method_name}"
|
78
|
+
puts ''
|
79
|
+
help
|
80
|
+
exit 1
|
273
81
|
end
|
274
82
|
|
275
|
-
def
|
276
|
-
|
277
|
-
when 'json'
|
278
|
-
save_json_results(results)
|
279
|
-
when 'yaml'
|
280
|
-
save_yaml_results(results)
|
281
|
-
when 'html'
|
282
|
-
generate_html_reports(results)
|
283
|
-
else
|
284
|
-
# Generate all formats
|
285
|
-
save_json_results(results)
|
286
|
-
save_yaml_results(results)
|
287
|
-
generate_html_reports(results)
|
288
|
-
end
|
289
|
-
|
290
|
-
show_generated_files
|
291
|
-
end
|
292
|
-
|
293
|
-
def save_json_results(results)
|
294
|
-
FileUtils.mkdir_p('results/data')
|
295
|
-
|
296
|
-
# Add Ruby version to results
|
297
|
-
results[:ruby_version] = RUBY_VERSION
|
298
|
-
results[:ruby_platform] = RUBY_PLATFORM
|
299
|
-
results[:timestamp] = Time.now.iso8601
|
300
|
-
|
301
|
-
File.write('results/data/results.json', JSON.pretty_generate(results))
|
302
|
-
say 'JSON results saved to: results/data/results.json', :green
|
303
|
-
end
|
304
|
-
|
305
|
-
def save_yaml_results(results)
|
306
|
-
FileUtils.mkdir_p('results/data')
|
307
|
-
|
308
|
-
# Add Ruby version to results
|
309
|
-
results[:ruby_version] = RUBY_VERSION
|
310
|
-
results[:ruby_platform] = RUBY_PLATFORM
|
311
|
-
results[:timestamp] = Time.now.iso8601
|
312
|
-
|
313
|
-
File.write('results/data/results.yaml', results.to_yaml)
|
314
|
-
say 'YAML results saved to: results/data/results.yaml', :green
|
315
|
-
end
|
316
|
-
|
317
|
-
def generate_html_reports(results)
|
318
|
-
say 'Generating reports...', :yellow
|
319
|
-
report_files = Serialbench.generate_reports(results)
|
320
|
-
|
321
|
-
say 'Reports generated:', :green
|
322
|
-
say " HTML: #{report_files[:html]}", :white
|
323
|
-
say " CSS: #{report_files[:css]}", :white
|
324
|
-
end
|
325
|
-
|
326
|
-
def show_generated_files
|
327
|
-
case options[:output_format]
|
328
|
-
when 'json'
|
329
|
-
say 'Files generated:', :cyan
|
330
|
-
say ' JSON: results/data/results.json', :white
|
331
|
-
when 'yaml'
|
332
|
-
say 'Files generated:', :cyan
|
333
|
-
say ' YAML: results/data/results.yaml', :white
|
334
|
-
when 'html'
|
335
|
-
say 'Files generated:', :cyan
|
336
|
-
say ' HTML: results/reports/benchmark_report.html', :white
|
337
|
-
say ' Charts: results/charts/*.svg', :white
|
338
|
-
else
|
339
|
-
say 'Files generated:', :cyan
|
340
|
-
say ' JSON: results/data/results.json', :white
|
341
|
-
say ' YAML: results/data/results.yaml', :white
|
342
|
-
say ' HTML: results/reports/benchmark_report.html', :white
|
343
|
-
say ' Charts: results/charts/*.svg', :white
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
def show_summary(results)
|
348
|
-
return unless results[:parsing] || results[:generation]
|
349
|
-
|
350
|
-
say "\n" + '=' * 50, :green
|
351
|
-
say 'BENCHMARK SUMMARY', :green
|
352
|
-
say '=' * 50, :green
|
353
|
-
|
354
|
-
show_parsing_summary(results[:parsing]) if results[:parsing]
|
355
|
-
|
356
|
-
show_generation_summary(results[:generation]) if results[:generation]
|
357
|
-
|
358
|
-
return unless results[:memory_usage]
|
359
|
-
|
360
|
-
show_memory_summary(results[:memory_usage])
|
361
|
-
end
|
362
|
-
|
363
|
-
def show_parsing_summary(parsing_results)
|
364
|
-
say "\nParsing Performance (operations/second):", :cyan
|
365
|
-
|
366
|
-
%i[small medium large].each do |size|
|
367
|
-
next unless parsing_results[size]
|
368
|
-
|
369
|
-
say "\n #{size.capitalize} files:", :yellow
|
370
|
-
|
371
|
-
# Flatten the nested structure and sort by performance
|
372
|
-
flattened_results = []
|
373
|
-
parsing_results[size].each do |format, serializers|
|
374
|
-
serializers.each do |serializer_name, data|
|
375
|
-
flattened_results << ["#{format}/#{serializer_name}", data]
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
sorted_results = flattened_results.sort_by { |_, data| -data[:iterations_per_second] }
|
380
|
-
|
381
|
-
sorted_results.each do |serializer_name, data|
|
382
|
-
ops_per_sec = data[:iterations_per_second].round(2)
|
383
|
-
say " #{serializer_name}: #{ops_per_sec} ops/sec", :white
|
384
|
-
end
|
385
|
-
end
|
386
|
-
end
|
387
|
-
|
388
|
-
def show_generation_summary(generation_results)
|
389
|
-
say "\nGeneration Performance (operations/second):", :cyan
|
390
|
-
|
391
|
-
%i[small medium large].each do |size|
|
392
|
-
next unless generation_results[size]
|
393
|
-
|
394
|
-
say "\n #{size.capitalize} files:", :yellow
|
395
|
-
|
396
|
-
# Flatten the nested structure and sort by performance
|
397
|
-
flattened_results = []
|
398
|
-
generation_results[size].each do |format, serializers|
|
399
|
-
serializers.each do |serializer_name, data|
|
400
|
-
flattened_results << ["#{format}/#{serializer_name}", data]
|
401
|
-
end
|
402
|
-
end
|
403
|
-
|
404
|
-
sorted_results = flattened_results.sort_by { |_, data| -data[:iterations_per_second] }
|
405
|
-
|
406
|
-
sorted_results.each do |serializer_name, data|
|
407
|
-
ops_per_sec = data[:iterations_per_second].round(2)
|
408
|
-
say " #{serializer_name}: #{ops_per_sec} ops/sec", :white
|
409
|
-
end
|
410
|
-
end
|
411
|
-
end
|
412
|
-
|
413
|
-
def show_memory_summary(memory_results)
|
414
|
-
say "\nMemory Usage (MB):", :cyan
|
415
|
-
|
416
|
-
%i[small medium large].each do |size|
|
417
|
-
next unless memory_results[size]
|
418
|
-
|
419
|
-
say "\n #{size.capitalize} files:", :yellow
|
420
|
-
|
421
|
-
# Flatten the nested structure and sort by memory usage (ascending)
|
422
|
-
flattened_results = []
|
423
|
-
memory_results[size].each do |format, serializers|
|
424
|
-
serializers.each do |serializer_name, data|
|
425
|
-
flattened_results << ["#{format}/#{serializer_name}", data]
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
|
-
sorted_results = flattened_results.sort_by { |_, data| data[:allocated_memory] }
|
430
|
-
|
431
|
-
sorted_results.each do |serializer_name, data|
|
432
|
-
memory_mb = (data[:allocated_memory] / 1024.0 / 1024.0).round(2)
|
433
|
-
say " #{serializer_name}: #{memory_mb} MB", :white
|
434
|
-
end
|
435
|
-
end
|
83
|
+
def respond_to_missing?(method_name, include_private = false)
|
84
|
+
false
|
436
85
|
end
|
437
86
|
end
|
438
87
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'ostruct'
|
5
|
+
require_relative 'schema_validator'
|
6
|
+
|
7
|
+
module Serialbench
|
8
|
+
# Manages configuration loading and validation for Serialbench
|
9
|
+
class ConfigManager
|
10
|
+
class ConfigurationError < StandardError; end
|
11
|
+
|
12
|
+
SCHEMA_PATH = File.join(__dir__, '../../docs/serialbench_config_schema.yaml')
|
13
|
+
|
14
|
+
# Load and validate configuration from file
|
15
|
+
def self.load_and_validate(config_path)
|
16
|
+
new.load_and_validate(config_path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@validator = SchemaValidator.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Load configuration file and validate against schema
|
24
|
+
def load_and_validate(config_path)
|
25
|
+
raise ConfigurationError, "Configuration file not found: #{config_path}" unless File.exist?(config_path)
|
26
|
+
|
27
|
+
begin
|
28
|
+
config_data = YAML.load_file(config_path)
|
29
|
+
rescue Psych::SyntaxError => e
|
30
|
+
raise ConfigurationError, "Invalid YAML syntax in #{config_path}: #{e.message}"
|
31
|
+
rescue StandardError => e
|
32
|
+
raise ConfigurationError, "Error reading configuration file #{config_path}: #{e.message}"
|
33
|
+
end
|
34
|
+
|
35
|
+
validate_config(config_data, config_path)
|
36
|
+
normalize_config(config_data)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Validate configuration against schema
|
42
|
+
def validate_config(config_data, config_path)
|
43
|
+
# For now, perform basic validation since we don't have a specific config schema validator
|
44
|
+
validate_basic_config(config_data)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Load the configuration schema
|
48
|
+
def load_schema
|
49
|
+
unless File.exist?(SCHEMA_PATH)
|
50
|
+
raise ConfigurationError, "Configuration schema not found: #{SCHEMA_PATH}"
|
51
|
+
end
|
52
|
+
|
53
|
+
begin
|
54
|
+
YAML.load_file(SCHEMA_PATH)
|
55
|
+
rescue StandardError => e
|
56
|
+
raise ConfigurationError, "Error loading configuration schema: #{e.message}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Normalize and convert configuration to structured object
|
61
|
+
def normalize_config(config_data)
|
62
|
+
config = OpenStruct.new(config_data)
|
63
|
+
|
64
|
+
# Apply defaults
|
65
|
+
config.output_dir ||= 'benchmark-results'
|
66
|
+
config.benchmark_config ||= 'config/full.yml'
|
67
|
+
config.auto_install = true if config.auto_install.nil?
|
68
|
+
|
69
|
+
# Validate runtime-specific requirements
|
70
|
+
case config.runtime
|
71
|
+
when 'docker'
|
72
|
+
validate_docker_config(config)
|
73
|
+
when 'asdf'
|
74
|
+
validate_asdf_config(config)
|
75
|
+
else
|
76
|
+
raise ConfigurationError, "Unknown runtime: #{config.runtime}"
|
77
|
+
end
|
78
|
+
|
79
|
+
config
|
80
|
+
end
|
81
|
+
|
82
|
+
# Validate Docker-specific configuration
|
83
|
+
def validate_docker_config(config)
|
84
|
+
unless config.image_variants && !config.image_variants.empty?
|
85
|
+
raise ConfigurationError, "Docker runtime requires 'image_variants' to be specified"
|
86
|
+
end
|
87
|
+
|
88
|
+
invalid_variants = config.image_variants - %w[slim alpine]
|
89
|
+
unless invalid_variants.empty?
|
90
|
+
raise ConfigurationError, "Invalid image variants: #{invalid_variants.join(', ')}. Valid variants: slim, alpine"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Validate ASDF-specific configuration
|
95
|
+
def validate_asdf_config(config)
|
96
|
+
# Check if ASDF is available
|
97
|
+
unless command_available?('asdf')
|
98
|
+
raise ConfigurationError, "ASDF is not installed or not in PATH. Please install ASDF to use asdf runtime."
|
99
|
+
end
|
100
|
+
|
101
|
+
# Validate Ruby version format for ASDF (should include patch version)
|
102
|
+
config.ruby_versions.each do |version|
|
103
|
+
unless version.match?(/^\d+\.\d+\.\d+$/)
|
104
|
+
raise ConfigurationError, "ASDF runtime requires full version numbers (e.g., '3.2.8'), got: #{version}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Basic configuration validation
|
110
|
+
def validate_basic_config(config_data)
|
111
|
+
# Check required fields
|
112
|
+
required_fields = %w[runtime ruby_versions output_dir benchmark_config]
|
113
|
+
required_fields.each do |field|
|
114
|
+
unless config_data.key?(field)
|
115
|
+
raise ConfigurationError, "Missing required field: #{field}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Validate runtime
|
120
|
+
valid_runtimes = %w[docker asdf]
|
121
|
+
unless valid_runtimes.include?(config_data['runtime'])
|
122
|
+
raise ConfigurationError, "Invalid runtime: #{config_data['runtime']}. Valid runtimes: #{valid_runtimes.join(', ')}"
|
123
|
+
end
|
124
|
+
|
125
|
+
# Validate ruby_versions is an array
|
126
|
+
unless config_data['ruby_versions'].is_a?(Array)
|
127
|
+
raise ConfigurationError, "ruby_versions must be an array"
|
128
|
+
end
|
129
|
+
|
130
|
+
if config_data['ruby_versions'].empty?
|
131
|
+
raise ConfigurationError, "ruby_versions cannot be empty"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Check if a command is available in PATH
|
136
|
+
def command_available?(command)
|
137
|
+
system("which #{command} > /dev/null 2>&1")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|