serialbench 0.1.1 → 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 +13 -5
- data/.github/workflows/docker.yml +35 -9
- data/.github/workflows/rake.yml +15 -0
- data/Gemfile +2 -1
- data/README.adoc +267 -1129
- 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.benchmark → Dockerfile.ubuntu} +4 -3
- data/docker/README.md +2 -2
- data/exe/serialbench +1 -1
- data/lib/serialbench/benchmark_runner.rb +261 -423
- 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 +58 -601
- 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/yajl_serializer.rb +0 -2
- 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 +0 -2
- 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 +0 -2
- data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +5 -1
- data/lib/serialbench/serializers/yaml/syck_serializer.rb +59 -22
- data/lib/serialbench/serializers.rb +23 -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 +4 -1
- metadata +86 -16
- data/config/ci.yml +0 -22
- data/config/full.yml +0 -30
- data/docker/run-benchmarks.sh +0 -356
- 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,630 +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 yaml toml],
|
21
|
-
desc: 'Formats to benchmark (xml, json, yaml, 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
|
-
}
|
61
|
-
|
62
|
-
runner = Serialbench::BenchmarkRunner.new(**runner_options)
|
63
|
-
|
64
|
-
begin
|
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
|
73
|
-
end
|
74
|
-
|
75
|
-
desc 'list', 'List available serializers'
|
76
|
-
long_desc <<~DESC
|
77
|
-
Display all available serialization libraries grouped by format.
|
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
|
96
|
-
else
|
97
|
-
%i[xml json yaml toml].each do |format|
|
98
|
-
serializers = Serialbench::Serializers.available_for_format(format)
|
99
|
-
next if serializers.empty?
|
100
|
-
|
101
|
-
show_serializers_for_format(format, serializers)
|
102
|
-
say ''
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
22
|
+
desc 'ruby_build SUBCOMMAND', 'Manage Ruby-Build definitions for validation'
|
23
|
+
subcommand 'ruby_build', Serialbench::Cli::RubyBuildCli
|
106
24
|
|
107
25
|
desc 'version', 'Show Serialbench version'
|
108
|
-
def version
|
109
|
-
|
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.
|
150
|
-
|
151
|
-
This command combines merge_results and HTML generation in one step.
|
152
|
-
|
153
|
-
Example:
|
154
|
-
serialbench github_pages ruby-3.0/results ruby-3.1/results ruby-3.2/results docs/
|
155
|
-
DESC
|
156
|
-
def github_pages(*args)
|
157
|
-
if args.length < 2
|
158
|
-
say 'Error: Need at least one input directory and one output directory', :red
|
159
|
-
say 'Usage: serialbench github_pages INPUT_DIRS... OUTPUT_DIR', :yellow
|
160
|
-
exit 1
|
161
|
-
end
|
162
|
-
|
163
|
-
output_dir = args.pop
|
164
|
-
input_dirs = args
|
165
|
-
|
166
|
-
say "Generating GitHub Pages from #{input_dirs.length} benchmark directories", :green
|
167
|
-
|
168
|
-
begin
|
169
|
-
merger = Serialbench::ResultMerger.new
|
170
|
-
|
171
|
-
# Merge results
|
172
|
-
say 'Step 1: Merging benchmark results...', :yellow
|
173
|
-
merger.merge_directories(input_dirs, output_dir)
|
174
|
-
|
175
|
-
# Generate GitHub Pages HTML
|
176
|
-
say 'Step 2: Generating GitHub Pages HTML...', :yellow
|
177
|
-
files = merger.generate_github_pages_html(output_dir)
|
178
|
-
|
179
|
-
say 'GitHub Pages generated successfully!', :green
|
180
|
-
say 'Files created:', :cyan
|
181
|
-
say " HTML: #{files[:html]}", :white
|
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
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
desc 'generate_reports DATA_FILE', 'Generate reports from benchmark data'
|
195
|
-
long_desc <<~DESC
|
196
|
-
Generate HTML and AsciiDoc reports from existing benchmark data.
|
197
|
-
|
198
|
-
DATA_FILE should be a JSON file containing benchmark results.
|
199
|
-
DESC
|
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
|
-
desc 'analyze_performance INPUT_DIRS... OUTPUT_FILE', 'Analyze performance across multiple benchmark results'
|
218
|
-
long_desc <<~DESC
|
219
|
-
Analyze performance data from multiple benchmark runs and generate JSON analysis.
|
220
|
-
|
221
|
-
INPUT_DIRS should contain results.json files from different benchmark runs.
|
222
|
-
OUTPUT_FILE will be a JSON file with detailed performance analysis.
|
223
|
-
|
224
|
-
Example:
|
225
|
-
serialbench analyze_performance artifacts/benchmark-results-*/ performance_analysis.json
|
226
|
-
DESC
|
227
|
-
def analyze_performance(*args)
|
228
|
-
if args.length < 2
|
229
|
-
say 'Error: Need at least one input directory and one output file', :red
|
230
|
-
say 'Usage: serialbench analyze_performance INPUT_DIRS... OUTPUT_FILE', :yellow
|
231
|
-
exit 1
|
232
|
-
end
|
233
|
-
|
234
|
-
output_file = args.pop
|
235
|
-
input_dirs = args
|
236
|
-
|
237
|
-
say "Analyzing performance from #{input_dirs.length} directories", :green
|
238
|
-
|
239
|
-
begin
|
240
|
-
results = []
|
241
|
-
|
242
|
-
input_dirs.each do |input_dir|
|
243
|
-
results_file = File.join(input_dir, 'data', 'results.json')
|
244
|
-
next unless File.exist?(results_file)
|
245
|
-
|
246
|
-
# Extract platform and ruby version from directory name
|
247
|
-
match = input_dir.match(/benchmark-results-([^-]+)-ruby-([^\/]+)/)
|
248
|
-
next unless match
|
249
|
-
|
250
|
-
platform = match[1]
|
251
|
-
ruby_version = match[2]
|
252
|
-
|
253
|
-
begin
|
254
|
-
data = JSON.parse(File.read(results_file))
|
255
|
-
|
256
|
-
# Process parsing results
|
257
|
-
data['parsing']&.each do |format, serializers|
|
258
|
-
serializers.each do |serializer, sizes|
|
259
|
-
sizes.each do |size, metrics|
|
260
|
-
results << {
|
261
|
-
platform: platform,
|
262
|
-
ruby_version: ruby_version,
|
263
|
-
format: format,
|
264
|
-
serializer: serializer,
|
265
|
-
size: size,
|
266
|
-
operation: 'parsing',
|
267
|
-
time_ms: metrics['average_time'] || 0,
|
268
|
-
memory_mb: metrics['memory_usage'] || 0,
|
269
|
-
iterations_per_second: metrics['iterations_per_second'] || 0
|
270
|
-
}
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
# Process generation results
|
276
|
-
data['generation']&.each do |format, serializers|
|
277
|
-
serializers.each do |serializer, sizes|
|
278
|
-
sizes.each do |size, metrics|
|
279
|
-
results << {
|
280
|
-
platform: platform,
|
281
|
-
ruby_version: ruby_version,
|
282
|
-
format: format,
|
283
|
-
serializer: serializer,
|
284
|
-
size: size,
|
285
|
-
operation: 'generation',
|
286
|
-
time_ms: metrics['average_time'] || 0,
|
287
|
-
memory_mb: metrics['memory_usage'] || 0,
|
288
|
-
iterations_per_second: metrics['iterations_per_second'] || 0
|
289
|
-
}
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
rescue JSON::ParserError => e
|
294
|
-
say "Warning: Could not parse #{results_file}: #{e.message}", :yellow
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
# Generate analysis report
|
299
|
-
analysis_report = {
|
300
|
-
'summary' => 'Cross-platform performance analysis',
|
301
|
-
'generated_at' => Time.now.iso8601,
|
302
|
-
'total_data_points' => results.length,
|
303
|
-
'platforms' => results.map { |r| r[:platform] }.uniq.sort,
|
304
|
-
'ruby_versions' => results.map { |r| r[:ruby_version] }.uniq.sort,
|
305
|
-
'formats' => results.map { |r| r[:format] }.uniq.sort,
|
306
|
-
'serializers' => results.map { |r| r[:serializer] }.uniq.sort,
|
307
|
-
'operations' => results.map { |r| r[:operation] }.uniq.sort,
|
308
|
-
'data' => results
|
309
|
-
}
|
310
|
-
|
311
|
-
# Write JSON analysis
|
312
|
-
File.write(output_file, JSON.pretty_generate(analysis_report))
|
313
|
-
|
314
|
-
say "Performance analysis generated with #{results.length} data points", :green
|
315
|
-
say "Platforms: #{analysis_report['platforms'].join(', ')}", :cyan
|
316
|
-
say "Ruby versions: #{analysis_report['ruby_versions'].join(', ')}", :cyan
|
317
|
-
say "Formats: #{analysis_report['formats'].join(', ')}", :cyan
|
318
|
-
say "Output saved to: #{output_file}", :green
|
319
|
-
rescue StandardError => e
|
320
|
-
say "Error analyzing performance: #{e.message}", :red
|
321
|
-
exit 1
|
322
|
-
end
|
26
|
+
def self.version
|
27
|
+
puts "Serialbench version #{Serialbench::VERSION}"
|
323
28
|
end
|
324
29
|
|
325
|
-
desc '
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
JSON_FILE should be the output from analyze_performance command.
|
330
|
-
OUTPUT_FILE will be a JSON file with platform comparison statistics.
|
331
|
-
|
332
|
-
Example:
|
333
|
-
serialbench platform_comparison performance_analysis.json platform_comparison.json
|
334
|
-
DESC
|
335
|
-
def platform_comparison(json_file, output_file)
|
336
|
-
say "Generating platform comparison from #{json_file}", :green
|
337
|
-
|
338
|
-
unless File.exist?(json_file)
|
339
|
-
say "JSON file does not exist: #{json_file}", :red
|
340
|
-
exit 1
|
341
|
-
end
|
342
|
-
|
343
|
-
begin
|
344
|
-
# Read the performance analysis JSON
|
345
|
-
analysis_data = JSON.parse(File.read(json_file))
|
346
|
-
data_points = analysis_data['data']
|
347
|
-
|
348
|
-
# Group by platform and calculate averages
|
349
|
-
platform_stats = {}
|
350
|
-
|
351
|
-
data_points.each do |point|
|
352
|
-
platform = point['platform']
|
353
|
-
format = point['format']
|
354
|
-
operation = point['operation']
|
355
|
-
time = point['time_ms'].to_f
|
356
|
-
|
357
|
-
platform_stats[platform] ||= {}
|
358
|
-
platform_stats[platform][format] ||= {}
|
359
|
-
platform_stats[platform][format][operation] ||= []
|
360
|
-
platform_stats[platform][format][operation] << time
|
361
|
-
end
|
362
|
-
|
363
|
-
# Calculate averages and generate report
|
364
|
-
report = {
|
365
|
-
'summary' => 'Cross-platform performance comparison',
|
366
|
-
'generated_at' => Time.now.iso8601,
|
367
|
-
'source_analysis' => json_file,
|
368
|
-
'total_platforms' => platform_stats.keys.length,
|
369
|
-
'platforms' => {}
|
370
|
-
}
|
371
|
-
|
372
|
-
platform_stats.each do |platform, formats|
|
373
|
-
report['platforms'][platform] = {}
|
374
|
-
formats.each do |format, operations|
|
375
|
-
report['platforms'][platform][format] = {}
|
376
|
-
operations.each do |operation, times|
|
377
|
-
avg_time = times.sum / times.length
|
378
|
-
report['platforms'][platform][format][operation] = {
|
379
|
-
'average_time_ms' => avg_time.round(3),
|
380
|
-
'sample_count' => times.length,
|
381
|
-
'min_time_ms' => times.min.round(3),
|
382
|
-
'max_time_ms' => times.max.round(3),
|
383
|
-
'std_deviation' => calculate_std_deviation(times).round(3)
|
384
|
-
}
|
385
|
-
end
|
386
|
-
end
|
387
|
-
end
|
388
|
-
|
389
|
-
# Write JSON report
|
390
|
-
File.write(output_file, JSON.pretty_generate(report))
|
391
|
-
|
392
|
-
say "Platform comparison report generated", :green
|
393
|
-
say "Platforms analyzed: #{platform_stats.keys.sort.join(', ')}", :cyan
|
394
|
-
say "Output saved to: #{output_file}", :green
|
395
|
-
rescue StandardError => e
|
396
|
-
say "Error generating platform comparison: #{e.message}", :red
|
397
|
-
exit 1
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
private
|
402
|
-
|
403
|
-
def show_available_serializers(formats)
|
404
|
-
say "\nAvailable serializers:", :cyan
|
405
|
-
|
406
|
-
formats.each do |format|
|
407
|
-
serializers = Serialbench::Serializers.available_for_format(format)
|
408
|
-
next if serializers.empty?
|
409
|
-
|
410
|
-
serializer_names = serializers.map do |serializer_class|
|
411
|
-
serializer = serializer_class.new
|
412
|
-
"#{serializer.name} v#{serializer.version}"
|
413
|
-
end
|
414
|
-
|
415
|
-
say " #{format.upcase}: #{serializer_names.join(', ')}", :white
|
416
|
-
end
|
417
|
-
|
418
|
-
say "\nTest data sizes: small, medium, large", :cyan
|
419
|
-
say ''
|
420
|
-
end
|
421
|
-
|
422
|
-
def show_serializers_for_format(format, serializers)
|
423
|
-
say "#{format.upcase}:", :cyan
|
424
|
-
|
425
|
-
serializers.each do |serializer_class|
|
426
|
-
serializer = serializer_class.new
|
427
|
-
features = []
|
428
|
-
features << 'streaming' if serializer.supports_streaming?
|
429
|
-
features << 'built-in' if %w[json rexml psych].include?(serializer.name)
|
430
|
-
|
431
|
-
feature_text = features.empty? ? '' : " (#{features.join(', ')})"
|
432
|
-
say " ✓ #{serializer.name} v#{serializer.version}#{feature_text}", :green
|
433
|
-
end
|
434
|
-
end
|
435
|
-
|
436
|
-
def run_selected_benchmarks(runner)
|
437
|
-
results = { environment: runner.environment_info }
|
438
|
-
|
439
|
-
if options[:parsing_only]
|
440
|
-
say 'Running parsing benchmarks...', :yellow
|
441
|
-
results[:parsing] = runner.run_parsing_benchmarks
|
442
|
-
elsif options[:generation_only]
|
443
|
-
say 'Running generation benchmarks...', :yellow
|
444
|
-
results[:generation] = runner.run_generation_benchmarks
|
445
|
-
elsif options[:streaming_only]
|
446
|
-
say 'Running streaming benchmarks...', :yellow
|
447
|
-
results[:streaming] = runner.run_streaming_benchmarks
|
448
|
-
elsif options[:memory_only]
|
449
|
-
say 'Running memory benchmarks...', :yellow
|
450
|
-
results[:memory_usage] = runner.run_memory_benchmarks
|
30
|
+
desc 'help [COMMAND]', 'Show help for commands'
|
31
|
+
def help(command = nil)
|
32
|
+
if command
|
33
|
+
super(command)
|
451
34
|
else
|
452
|
-
|
453
|
-
|
454
|
-
end
|
35
|
+
puts <<~HELP
|
36
|
+
Serialbench - Benchmarking Framework for Ruby Serialization Libraries
|
455
37
|
|
456
|
-
|
457
|
-
|
38
|
+
USAGE:
|
39
|
+
serialbench COMMAND [SUBCOMMAND] [OPTIONS]
|
458
40
|
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
generate_html_reports(results)
|
467
|
-
else
|
468
|
-
# Generate all formats
|
469
|
-
save_json_results(results)
|
470
|
-
save_yaml_results(results)
|
471
|
-
generate_html_reports(results)
|
472
|
-
end
|
473
|
-
|
474
|
-
show_generated_files
|
475
|
-
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
|
476
48
|
|
477
|
-
|
478
|
-
|
49
|
+
EXAMPLES:
|
50
|
+
# Create a Docker environment
|
51
|
+
serialbench environment new docker-test docker
|
479
52
|
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
results[:timestamp] = Time.now.iso8601
|
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
|
484
56
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
def save_yaml_results(results)
|
490
|
-
FileUtils.mkdir_p('results/data')
|
57
|
+
# Create and execute a benchmark
|
58
|
+
serialbench benchmark create my-benchmark
|
59
|
+
serialbench benchmark execute my-benchmark.yml
|
491
60
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
results[:timestamp] = Time.now.iso8601
|
496
|
-
|
497
|
-
File.write('results/data/results.yaml', results.to_yaml)
|
498
|
-
say 'YAML results saved to: results/data/results.yaml', :green
|
499
|
-
end
|
61
|
+
# Create a result set for comparison
|
62
|
+
serialbench resultset create comparison-set
|
63
|
+
serialbench resultset add-result comparison-set results/my-benchmark
|
500
64
|
|
501
|
-
|
502
|
-
|
503
|
-
|
65
|
+
# Generate static sites
|
66
|
+
serialbench benchmark build-site results/my-benchmark
|
67
|
+
serialbench resultset build-site resultsets/comparison-set
|
504
68
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
end
|
509
|
-
|
510
|
-
def show_generated_files
|
511
|
-
case options[:output_format]
|
512
|
-
when 'json'
|
513
|
-
say 'Files generated:', :cyan
|
514
|
-
say ' JSON: results/data/results.json', :white
|
515
|
-
when 'yaml'
|
516
|
-
say 'Files generated:', :cyan
|
517
|
-
say ' YAML: results/data/results.yaml', :white
|
518
|
-
when 'html'
|
519
|
-
say 'Files generated:', :cyan
|
520
|
-
say ' HTML: results/reports/benchmark_report.html', :white
|
521
|
-
say ' Charts: results/charts/*.svg', :white
|
522
|
-
else
|
523
|
-
say 'Files generated:', :cyan
|
524
|
-
say ' JSON: results/data/results.json', :white
|
525
|
-
say ' YAML: results/data/results.yaml', :white
|
526
|
-
say ' HTML: results/reports/benchmark_report.html', :white
|
527
|
-
say ' Charts: results/charts/*.svg', :white
|
69
|
+
For detailed help on any command, use:
|
70
|
+
serialbench COMMAND help
|
71
|
+
HELP
|
528
72
|
end
|
529
73
|
end
|
530
74
|
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
show_parsing_summary(results[:parsing]) if results[:parsing]
|
539
|
-
|
540
|
-
show_generation_summary(results[:generation]) if results[:generation]
|
541
|
-
|
542
|
-
return unless results[:memory_usage]
|
543
|
-
|
544
|
-
show_memory_summary(results[:memory_usage])
|
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
|
545
81
|
end
|
546
82
|
|
547
|
-
def
|
548
|
-
|
549
|
-
|
550
|
-
%i[small medium large].each do |size|
|
551
|
-
next unless parsing_results[size]
|
552
|
-
|
553
|
-
say "\n #{size.capitalize} files:", :yellow
|
554
|
-
|
555
|
-
# Flatten the nested structure and sort by performance
|
556
|
-
flattened_results = []
|
557
|
-
parsing_results[size].each do |format, serializers|
|
558
|
-
serializers.each do |serializer_name, data|
|
559
|
-
flattened_results << ["#{format}/#{serializer_name}", data]
|
560
|
-
end
|
561
|
-
end
|
562
|
-
|
563
|
-
sorted_results = flattened_results.sort_by { |_, data| -data[:iterations_per_second] }
|
564
|
-
|
565
|
-
sorted_results.each do |serializer_name, data|
|
566
|
-
ops_per_sec = data[:iterations_per_second].round(2)
|
567
|
-
say " #{serializer_name}: #{ops_per_sec} ops/sec", :white
|
568
|
-
end
|
569
|
-
end
|
570
|
-
end
|
571
|
-
|
572
|
-
def show_generation_summary(generation_results)
|
573
|
-
say "\nGeneration Performance (operations/second):", :cyan
|
574
|
-
|
575
|
-
%i[small medium large].each do |size|
|
576
|
-
next unless generation_results[size]
|
577
|
-
|
578
|
-
say "\n #{size.capitalize} files:", :yellow
|
579
|
-
|
580
|
-
# Flatten the nested structure and sort by performance
|
581
|
-
flattened_results = []
|
582
|
-
generation_results[size].each do |format, serializers|
|
583
|
-
serializers.each do |serializer_name, data|
|
584
|
-
flattened_results << ["#{format}/#{serializer_name}", data]
|
585
|
-
end
|
586
|
-
end
|
587
|
-
|
588
|
-
sorted_results = flattened_results.sort_by { |_, data| -data[:iterations_per_second] }
|
589
|
-
|
590
|
-
sorted_results.each do |serializer_name, data|
|
591
|
-
ops_per_sec = data[:iterations_per_second].round(2)
|
592
|
-
say " #{serializer_name}: #{ops_per_sec} ops/sec", :white
|
593
|
-
end
|
594
|
-
end
|
595
|
-
end
|
596
|
-
|
597
|
-
def show_memory_summary(memory_results)
|
598
|
-
say "\nMemory Usage (MB):", :cyan
|
599
|
-
|
600
|
-
%i[small medium large].each do |size|
|
601
|
-
next unless memory_results[size]
|
602
|
-
|
603
|
-
say "\n #{size.capitalize} files:", :yellow
|
604
|
-
|
605
|
-
# Flatten the nested structure and sort by memory usage (ascending)
|
606
|
-
flattened_results = []
|
607
|
-
memory_results[size].each do |format, serializers|
|
608
|
-
serializers.each do |serializer_name, data|
|
609
|
-
flattened_results << ["#{format}/#{serializer_name}", data]
|
610
|
-
end
|
611
|
-
end
|
612
|
-
|
613
|
-
sorted_results = flattened_results.sort_by { |_, data| data[:allocated_memory] }
|
614
|
-
|
615
|
-
sorted_results.each do |serializer_name, data|
|
616
|
-
memory_mb = (data[:allocated_memory] / 1024.0 / 1024.0).round(2)
|
617
|
-
say " #{serializer_name}: #{memory_mb} MB", :white
|
618
|
-
end
|
619
|
-
end
|
620
|
-
end
|
621
|
-
|
622
|
-
def calculate_std_deviation(values)
|
623
|
-
return 0.0 if values.length <= 1
|
624
|
-
|
625
|
-
mean = values.sum.to_f / values.length
|
626
|
-
variance = values.map { |v| (v - mean)**2 }.sum / values.length
|
627
|
-
Math.sqrt(variance)
|
83
|
+
def respond_to_missing?(method_name, include_private = false)
|
84
|
+
false
|
628
85
|
end
|
629
86
|
end
|
630
87
|
end
|