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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +181 -30
  3. data/.github/workflows/ci.yml +3 -3
  4. data/.github/workflows/docker.yml +272 -0
  5. data/.github/workflows/rake.yml +15 -0
  6. data/.github/workflows/release.yml +25 -0
  7. data/Gemfile +6 -30
  8. data/README.adoc +381 -415
  9. data/Rakefile +0 -55
  10. data/config/benchmarks/full.yml +29 -0
  11. data/config/benchmarks/short.yml +26 -0
  12. data/config/environments/asdf-ruby-3.2.yml +8 -0
  13. data/config/environments/asdf-ruby-3.3.yml +8 -0
  14. data/config/environments/docker-ruby-3.0.yml +9 -0
  15. data/config/environments/docker-ruby-3.1.yml +9 -0
  16. data/config/environments/docker-ruby-3.2.yml +9 -0
  17. data/config/environments/docker-ruby-3.3.yml +9 -0
  18. data/config/environments/docker-ruby-3.4.yml +9 -0
  19. data/docker/Dockerfile.alpine +33 -0
  20. data/docker/Dockerfile.ubuntu +32 -0
  21. data/docker/README.md +214 -0
  22. data/exe/serialbench +1 -1
  23. data/lib/serialbench/benchmark_runner.rb +270 -350
  24. data/lib/serialbench/cli/base_cli.rb +51 -0
  25. data/lib/serialbench/cli/benchmark_cli.rb +380 -0
  26. data/lib/serialbench/cli/environment_cli.rb +181 -0
  27. data/lib/serialbench/cli/resultset_cli.rb +215 -0
  28. data/lib/serialbench/cli/ruby_build_cli.rb +238 -0
  29. data/lib/serialbench/cli.rb +59 -410
  30. data/lib/serialbench/config_manager.rb +140 -0
  31. data/lib/serialbench/models/benchmark_config.rb +63 -0
  32. data/lib/serialbench/models/benchmark_result.rb +45 -0
  33. data/lib/serialbench/models/environment_config.rb +71 -0
  34. data/lib/serialbench/models/platform.rb +59 -0
  35. data/lib/serialbench/models/result.rb +53 -0
  36. data/lib/serialbench/models/result_set.rb +71 -0
  37. data/lib/serialbench/models/result_store.rb +108 -0
  38. data/lib/serialbench/models.rb +54 -0
  39. data/lib/serialbench/ruby_build_manager.rb +153 -0
  40. data/lib/serialbench/runners/asdf_runner.rb +296 -0
  41. data/lib/serialbench/runners/base.rb +32 -0
  42. data/lib/serialbench/runners/docker_runner.rb +142 -0
  43. data/lib/serialbench/serializers/base_serializer.rb +8 -16
  44. data/lib/serialbench/serializers/json/base_json_serializer.rb +4 -4
  45. data/lib/serialbench/serializers/json/json_serializer.rb +0 -2
  46. data/lib/serialbench/serializers/json/oj_serializer.rb +0 -2
  47. data/lib/serialbench/serializers/json/rapidjson_serializer.rb +50 -0
  48. data/lib/serialbench/serializers/json/yajl_serializer.rb +6 -4
  49. data/lib/serialbench/serializers/toml/base_toml_serializer.rb +5 -3
  50. data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +0 -2
  51. data/lib/serialbench/serializers/toml/tomlib_serializer.rb +0 -2
  52. data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +56 -0
  53. data/lib/serialbench/serializers/xml/base_xml_serializer.rb +4 -9
  54. data/lib/serialbench/serializers/xml/libxml_serializer.rb +0 -2
  55. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +21 -5
  56. data/lib/serialbench/serializers/xml/oga_serializer.rb +0 -2
  57. data/lib/serialbench/serializers/xml/ox_serializer.rb +0 -2
  58. data/lib/serialbench/serializers/xml/rexml_serializer.rb +32 -4
  59. data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +59 -0
  60. data/lib/serialbench/serializers/yaml/psych_serializer.rb +54 -0
  61. data/lib/serialbench/serializers/yaml/syck_serializer.rb +102 -0
  62. data/lib/serialbench/serializers.rb +34 -6
  63. data/lib/serialbench/site_generator.rb +105 -0
  64. data/lib/serialbench/templates/assets/css/benchmark_report.css +535 -0
  65. data/lib/serialbench/templates/assets/css/format_based.css +526 -0
  66. data/lib/serialbench/templates/assets/css/themes.css +588 -0
  67. data/lib/serialbench/templates/assets/js/chart_helpers.js +381 -0
  68. data/lib/serialbench/templates/assets/js/dashboard.js +796 -0
  69. data/lib/serialbench/templates/assets/js/navigation.js +142 -0
  70. data/lib/serialbench/templates/base.liquid +49 -0
  71. data/lib/serialbench/templates/format_based.liquid +279 -0
  72. data/lib/serialbench/templates/partials/chart_section.liquid +4 -0
  73. data/lib/serialbench/version.rb +1 -1
  74. data/lib/serialbench.rb +2 -31
  75. data/serialbench.gemspec +28 -17
  76. metadata +192 -55
  77. data/lib/serialbench/chart_generator.rb +0 -821
  78. data/lib/serialbench/result_formatter.rb +0 -182
  79. data/lib/serialbench/result_merger.rb +0 -1201
  80. data/lib/serialbench/serializers/xml/base_parser.rb +0 -69
  81. data/lib/serialbench/serializers/xml/libxml_parser.rb +0 -98
  82. data/lib/serialbench/serializers/xml/nokogiri_parser.rb +0 -111
  83. data/lib/serialbench/serializers/xml/oga_parser.rb +0 -85
  84. data/lib/serialbench/serializers/xml/ox_parser.rb +0 -64
  85. data/lib/serialbench/serializers/xml/rexml_parser.rb +0 -129
@@ -0,0 +1,296 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+ require 'yaml'
6
+ require 'open3'
7
+ require 'stringio'
8
+ require_relative '../benchmark_runner'
9
+ require_relative 'base'
10
+
11
+ module Serialbench
12
+ # Handles ASDF-based benchmark execution
13
+ module Runners
14
+ class AsdfRunner < Base
15
+ class AsdfError < StandardError; end
16
+
17
+ def initialize(environment_config, environment_config_path)
18
+ super
19
+ validate_asdf_available
20
+ end
21
+
22
+ # Prepare Ruby versions via ASDF
23
+ def prepare
24
+ puts '💎 Preparing Ruby versions via ASDF...'
25
+
26
+ ruby_version = @environment_config.ruby_build_tag
27
+ installed_versions = get_installed_ruby_versions
28
+
29
+ unless installed_versions.include?(ruby_version)
30
+ if @environment_config.asdf&.auto_install
31
+ install_missing_versions([ruby_version])
32
+ else
33
+ raise AsdfError,
34
+ "Missing Ruby version: #{ruby_version}. Set auto_install: true to install automatically."
35
+ end
36
+ end
37
+
38
+ # Install gems for the Ruby version
39
+ install_gems_for_version(ruby_version)
40
+
41
+ puts '✅ Ruby version is prepared with gems installed'
42
+ end
43
+
44
+ # Run benchmark
45
+ def benchmark
46
+ puts '🚀 Running benchmarks...'
47
+
48
+ ruby_version = @environment_config.ruby_build_tag
49
+
50
+ raise AsdfError, 'Benchmark run failed' unless run_benchmark(ruby_version)
51
+
52
+ puts '✅ Completed 1 benchmark runs'
53
+ puts "📁 Individual results are available in: results/runs/#{@environment_name}"
54
+ puts '✅ ASDF benchmark completed successfully!'
55
+ puts "Results saved to: results/runs/#{@environment_name}"
56
+ puts "Generate site: serialbench benchmark build-site results/runs/#{@environment_name}"
57
+ end
58
+
59
+ private
60
+
61
+ # Validate ASDF is available
62
+ def validate_asdf_available
63
+ unless system('asdf --version > /dev/null 2>&1')
64
+ raise AsdfError,
65
+ 'ASDF is not installed or not available in PATH'
66
+ end
67
+
68
+ # Check if ruby plugin is installed
69
+ return if system('asdf plugin list | grep -q ruby')
70
+
71
+ raise AsdfError, 'ASDF ruby plugin is not installed. Run: asdf plugin add ruby'
72
+ end
73
+
74
+ # Get list of installed Ruby versions
75
+ def get_installed_ruby_versions
76
+ output = `asdf list ruby 2>/dev/null`.strip
77
+ return [] if output.empty?
78
+
79
+ output.split("\n").map(&:strip).reject(&:empty?).map do |line|
80
+ # Remove leading asterisk and whitespace
81
+ line.gsub(/^\*?\s*/, '')
82
+ end
83
+ end
84
+
85
+ # Install missing Ruby versions
86
+ def install_missing_versions(versions)
87
+ puts "📦 Installing missing Ruby versions: #{versions.join(', ')}"
88
+
89
+ versions.each do |version|
90
+ puts "🔨 Installing Ruby #{version}..."
91
+
92
+ # Create temporary log directory
93
+ Dir.mktmpdir('asdf_install_ruby') do |temp_log_dir|
94
+ # Create a temporary file for logging
95
+ install_log = File.join(temp_log_dir, "install-ruby-#{version}.log")
96
+
97
+ success = false
98
+ Dir.chdir(temp_log_dir) do
99
+ success = system("asdf install ruby #{version} > #{install_log} 2>&1")
100
+ end
101
+
102
+ if success
103
+ puts "✅ Installed Ruby #{version}"
104
+ else
105
+ puts "❌ Failed to install Ruby #{version} (see #{install_log})"
106
+ raise AsdfError, "Failed to install Ruby #{version}"
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ # Install gems for a specific Ruby version
113
+ def install_gems_for_version(ruby_version)
114
+ puts "🔧 Installing gems for Ruby #{ruby_version}..."
115
+
116
+ # Create temporary log directory
117
+ temp_log_dir = "results/asdf-#{ruby_version}"
118
+ FileUtils.mkdir_p(temp_log_dir)
119
+ gem_install_log = File.join(temp_log_dir, "gems-ruby-#{ruby_version}.log")
120
+
121
+ # Use ASDF to install bundler and the serialbench gem
122
+ puts " 📦 Installing bundler and serialbench for Ruby #{ruby_version}..."
123
+ env = { 'ASDF_RUBY_VERSION' => ruby_version }
124
+ cmd = ['asdf', 'exec', 'gem', 'install', 'bundler', 'serialbench', '--no-document']
125
+
126
+ success = system(env, *cmd, out: gem_install_log, err: gem_install_log)
127
+ unless success
128
+ puts "❌ Failed to install gems for Ruby #{ruby_version} (see #{gem_install_log})"
129
+ raise AsdfError, "Failed to install gems for Ruby #{ruby_version}"
130
+ end
131
+
132
+ # ASDF doesn't need reshash like rbenv - gems are immediately available
133
+ puts "✅ Gems installed for Ruby #{ruby_version}"
134
+ end
135
+
136
+ # Run benchmark for specific Ruby version
137
+ def run_benchmark(benchmark_config, benchmark_config_path, result_dir)
138
+ puts "🚀 Running benchmark for #{@name}..."
139
+
140
+ FileUtils.mkdir_p(result_dir)
141
+
142
+ prepare
143
+
144
+ ruby_version = @environment_config.ruby_build_tag
145
+ puts "🏃 Running benchmarks for Ruby #{ruby_version}..."
146
+ puts " 📁 Results will be saved to: #{result_dir}"
147
+
148
+ benchmark_log = File.join(result_dir, 'benchmark.log')
149
+
150
+ # Run benchmark directly using BenchmarkRunner instead of CLI
151
+ puts ' 🚀 Starting benchmark execution...'
152
+
153
+ # Set ASDF Ruby version for this process
154
+ ENV['ASDF_RUBY_VERSION'] = ruby_version
155
+
156
+ # Capture stdout/stderr for logging
157
+ log_output = StringIO.new
158
+
159
+ runner = Serialbench::BenchmarkRunner.new(
160
+ benchmark_config: benchmark_config,
161
+ environment_config: @environment_config
162
+ )
163
+
164
+ # Run benchmarks and capture output
165
+ puts " Running benchmarks with #{benchmark_config.iterations} iterations..."
166
+
167
+ # Redirect stdout to capture benchmark output
168
+ original_stdout = $stdout
169
+ $stdout = log_output
170
+
171
+ results = runner.run_all_benchmarks
172
+
173
+ # Restore stdout
174
+ $stdout = original_stdout
175
+
176
+ # Save benchmark results to results.yaml
177
+ results_file = File.join(result_dir, 'results.yaml')
178
+
179
+ # Create platform string
180
+ require_relative 'models/platform'
181
+ platform = Serialbench::Models::Platform.current_local
182
+ platform_string = "asdf-#{platform.os}-#{platform.arch}-ruby-#{ruby_version}"
183
+
184
+ # Create comprehensive results structure with platform and metadata merged in
185
+ full_results = {
186
+ 'platform' => {
187
+ 'platform_string' => platform_string,
188
+ 'type' => 'asdf',
189
+ 'os' => platform.os,
190
+ 'arch' => platform.arch
191
+ },
192
+ 'metadata' => {
193
+ 'environment_name' => @environment_name,
194
+ 'benchmark_config' => @benchmark_config,
195
+ 'created_at' => Time.now.iso8601,
196
+ 'tags' => ['asdf', platform.os, platform.arch, "ruby-#{ruby_version}"]
197
+ },
198
+ 'environment' => {
199
+ 'name' => @environment_name,
200
+ 'type' => 'asdf',
201
+ 'ruby_build_tag' => ruby_version,
202
+ 'created_at' => Time.now.iso8601
203
+ },
204
+ 'config' => {
205
+ 'benchmark_config' => @benchmark_config,
206
+ 'formats' => config['formats'],
207
+ 'iterations' => config['iterations'],
208
+ 'data_sizes' => config['data_sizes']
209
+ },
210
+ 'results' => results
211
+ }
212
+
213
+ File.write(results_file, full_results.to_yaml)
214
+
215
+ # Save execution log
216
+ File.write(benchmark_log, log_output.string)
217
+
218
+ puts "✅ Completed Ruby #{ruby_version}"
219
+ puts " Results saved to: #{results_file}"
220
+ puts " Log saved to: #{benchmark_log}"
221
+ true
222
+ rescue StandardError => e
223
+ puts "❌ Failed Ruby #{ruby_version}: #{e.message}"
224
+ File.write(benchmark_log, "Error: #{e.message}\n#{e.backtrace.join("\n")}")
225
+ false
226
+ ensure
227
+ # Clean up environment variable and restore stdout
228
+ ENV.delete('ASDF_RUBY_VERSION')
229
+ $stdout = original_stdout if defined?(original_stdout)
230
+ end
231
+
232
+ # Process and merge results
233
+ def process_results(successful_runs)
234
+ puts "📊 Processing #{successful_runs} successful results..."
235
+
236
+ # Find all result directories with valid results
237
+ # Look for directories that contain results/runs subdirectories
238
+ result_dirs = Dir.glob(File.join(@output_dir, 'asdf-*')).select do |dir|
239
+ results_runs_dir = File.join(dir, 'results', 'runs')
240
+ Dir.exist?(results_runs_dir) && !Dir.glob(File.join(results_runs_dir, '*')).empty?
241
+ end
242
+
243
+ if result_dirs.empty?
244
+ puts '⚠️ No results found for processing, but benchmarks completed successfully!'
245
+ puts "📁 Individual results are available in: #{@output_dir}"
246
+ return
247
+ end
248
+
249
+ puts '🎉 Results processed successfully!'
250
+ puts "📁 Results directory: #{@output_dir}"
251
+ puts '📊 Individual benchmark results available in:'
252
+ result_dirs.each do |dir|
253
+ puts " - #{dir}"
254
+ end
255
+ puts ''
256
+ puts '💡 To create a comparison report, use:'
257
+ puts ' serialbench resultset new multi-ruby-comparison'
258
+ result_dirs.each do |dir|
259
+ result_name = File.basename(dir)
260
+ puts " serialbench resultset add-result multi-ruby-comparison #{result_name}"
261
+ end
262
+ puts ' serialbench resultset build-site multi-ruby-comparison'
263
+ end
264
+
265
+ # Generate combined platform string for directory naming
266
+ # Format: asdf-{os}-{arch}-ruby-{version}
267
+ def generate_platform_string(runner_type, ruby_version)
268
+ # Get OS name
269
+ os = case RUBY_PLATFORM
270
+ when /darwin/
271
+ 'macos'
272
+ when /linux/
273
+ 'linux'
274
+ when /mswin|mingw|cygwin/
275
+ 'windows'
276
+ else
277
+ 'unknown'
278
+ end
279
+
280
+ # Get architecture (simplified)
281
+ arch = case RUBY_PLATFORM
282
+ when /x86_64|amd64/
283
+ 'x64'
284
+ when /arm64|aarch64/
285
+ 'arm64'
286
+ when /i386|i686/
287
+ 'x86'
288
+ else
289
+ 'unknown'
290
+ end
291
+
292
+ "#{runner_type}-#{os}-#{arch}-ruby-#{ruby_version}"
293
+ end
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+ require 'yaml'
6
+ require 'open3'
7
+ require 'stringio'
8
+
9
+ module Serialbench
10
+ # Handles ASDF-based benchmark execution
11
+ module Runners
12
+ class Base
13
+ def initialize(environment_config, environment_config_path)
14
+ @environment_config = environment_config
15
+ @environment_config_path = environment_config_path
16
+
17
+ raise 'environment_config is required' unless @environment_config
18
+ raise 'environment_config_path is required' unless @environment_config_path
19
+ raise 'environment_config_path must be a valid file' unless File.exist?(@environment_config_path)
20
+ end
21
+
22
+ def prepare
23
+ raise NotImplementedError, 'Subclasses must implement the prepare method'
24
+ end
25
+
26
+ # Run benchmark
27
+ def benchmark
28
+ raise NotImplementedError, 'Subclasses must implement the benchmark method'
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+ require_relative '../ruby_build_manager'
6
+ require_relative 'base'
7
+
8
+ module Serialbench
9
+ module Runners
10
+ # Handles Docker-based benchmark execution
11
+ class DockerRunner < Base
12
+ class DockerError < StandardError; end
13
+
14
+ def initialize(environment_config, environment_config_path)
15
+ super
16
+
17
+ @docker_image = @environment_config.docker&.image
18
+ raise 'docker.image is required' unless @docker_image
19
+
20
+ # Handle dockerfile path relative to environment file
21
+ dockerfile_path = @environment_config.docker&.dockerfile
22
+ raise 'docker.dockerfile path is required' unless dockerfile_path
23
+
24
+ @dockerfile = Pathname.new(environment_config_path).dirname.join(dockerfile_path).to_s
25
+ raise "path '#{@dockerfile}' specified in docker.dockerfile is not found" unless File.exist?(@dockerfile)
26
+
27
+ validate_docker_available
28
+ end
29
+
30
+ # Build Docker image for this environment
31
+ def prepare
32
+ puts "🐳 Preparing Docker image for #{@name}..."
33
+
34
+ puts " 🐍 Using Docker image: #{base_image_string}"
35
+ puts " 🔨 Building Docker image: #{docker_image_string}"
36
+
37
+ cmd = [
38
+ 'docker', 'build',
39
+ '--build-arg', "BASE_IMAGE=#{base_image_string}",
40
+ '-t', docker_image_string,
41
+ '-f', @dockerfile,
42
+ '.'
43
+ ]
44
+
45
+ # Show command being run
46
+ puts " 🔧 Running command: #{cmd.join(' ')}"
47
+ success = system(*cmd)
48
+
49
+ raise DockerError, "Failed to build Docker image '#{docker_image_string}'" unless success
50
+
51
+ puts "✅ Docker image prepared successfully for #{docker_image_string}"
52
+ end
53
+
54
+ def base_image_string
55
+ @environment_config.docker.image
56
+ end
57
+
58
+ def docker_image_string
59
+ "serialbench:#{@environment_config.ruby_build_tag}"
60
+ end
61
+
62
+ # Run benchmark for this environment
63
+ def run_benchmark(benchmark_config, benchmark_config_path, result_dir)
64
+ puts "🚀 Running benchmark at #{@environment_config_path}..."
65
+
66
+ FileUtils.mkdir_p(result_dir)
67
+
68
+ # Build image if not already built
69
+ prepare unless image_exists?
70
+
71
+ # Run the benchmark
72
+ run_benchmark_in_container(benchmark_config, benchmark_config_path, result_dir)
73
+
74
+ puts "✅ Benchmark completed for #{@environment_config_path}"
75
+ end
76
+
77
+ private
78
+
79
+ # Validate Docker is available
80
+ def validate_docker_available
81
+ unless system('docker --version > /dev/null 2>&1')
82
+ raise DockerError, 'Docker is not installed or not available in PATH'
83
+ end
84
+
85
+ return if system('docker info > /dev/null 2>&1')
86
+
87
+ raise DockerError, 'Docker daemon is not running'
88
+ end
89
+
90
+ # Check if Docker image exists
91
+ def image_exists?
92
+ image_name = "serialbench:#{@name}"
93
+ system("docker image inspect #{image_name} > /dev/null 2>&1")
94
+ end
95
+
96
+ # Run benchmark in container
97
+ def run_benchmark_in_container(benchmark_config, benchmark_config_path, result_dir)
98
+ puts '🏃 Running benchmark in Docker container...'
99
+ puts " 📁 Results will be saved to: #{result_dir}"
100
+ puts " 🐳 Using Docker image: #{docker_image_string}"
101
+
102
+ benchmark_log = File.join(result_dir, 'benchmark.log')
103
+
104
+ cmd = [
105
+ 'docker', 'run', '--rm',
106
+ '-v', "#{File.expand_path(result_dir)}:/app/results",
107
+ '-v', "#{File.expand_path(@environment_config_path)}:/app/environment.yml",
108
+ '-v', "#{File.expand_path(benchmark_config_path)}:/app/benchmark_config.yml",
109
+ # TODO: do not hard code this path in the docker image
110
+ '-v', "#{RubyBuildManager::CACHE_FILE}:/root/.serialbench/ruby-build-definitions.yaml",
111
+ docker_image_string,
112
+ 'benchmark',
113
+ '_docker_execute',
114
+ '--result_dir=/app/results',
115
+ '/app/environment.yml',
116
+ '/app/benchmark_config.yml'
117
+ ]
118
+
119
+ puts " 🔧 Running command: #{cmd.join(' ')}"
120
+ puts " 📝 Benchmark output will be logged to: #{benchmark_log}"
121
+ puts ' ⏳ This may take several minutes...'
122
+
123
+ success = system("#{cmd.join(' ')} > #{benchmark_log} 2>&1")
124
+ puts " ✅ Command executed with status: #{success ? 'success' : 'failure'}"
125
+ puts " 📊 Checking if results saved to: #{File.join(result_dir, 'results.yaml')}"
126
+
127
+ if success && File.exist?(File.join(result_dir, 'results.yaml'))
128
+ puts '✅ Benchmark completed successfully'
129
+ puts " 📊 Results saved to: #{File.join(result_dir, 'results.yaml')}"
130
+ else
131
+ puts "❌ Benchmark failed (see #{benchmark_log})"
132
+ puts " 🔍 Check log file for details: #{benchmark_log}"
133
+ if File.exist?(benchmark_log)
134
+ puts ' 📄 Last few lines of log:'
135
+ system("tail -10 #{benchmark_log} | sed 's/^/ /'")
136
+ end
137
+ raise DockerError, 'Benchmark execution failed'
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -1,26 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'singleton'
4
+
3
5
  module Serialbench
4
6
  module Serializers
5
7
  class BaseSerializer
8
+ include Singleton
9
+
6
10
  def initialize
7
11
  # Override in subclasses
8
12
  end
9
13
 
10
- def available?
11
- raise NotImplementedError, 'Subclasses must implement #available?'
12
- end
13
-
14
- def name
15
- raise NotImplementedError, 'Subclasses must implement #name'
16
- end
17
-
18
- def version
19
- raise NotImplementedError, 'Subclasses must implement #version'
20
- end
21
-
22
- def format
23
- raise NotImplementedError, 'Subclasses must implement #format'
14
+ %w[name version format library_require_name available?].each do |method_name|
15
+ define_method(method_name) do
16
+ self.class.send(method_name)
17
+ end
24
18
  end
25
19
 
26
20
  def parse(data)
@@ -44,8 +38,6 @@ module Serialbench
44
38
  false
45
39
  end
46
40
 
47
- protected
48
-
49
41
  def require_library(library_name)
50
42
  require library_name
51
43
  true
@@ -6,7 +6,7 @@ module Serialbench
6
6
  module Serializers
7
7
  module Json
8
8
  class BaseJsonSerializer < BaseSerializer
9
- def format
9
+ def self.format
10
10
  :json
11
11
  end
12
12
 
@@ -29,7 +29,9 @@ module Serialbench
29
29
  }
30
30
  end
31
31
 
32
- protected
32
+ def supports_generation?
33
+ true
34
+ end
33
35
 
34
36
  def supports_pretty_print?
35
37
  true
@@ -48,8 +50,6 @@ module Serialbench
48
50
  raise NotImplementedError, 'Subclasses must implement #library_require_name'
49
51
  end
50
52
 
51
- public
52
-
53
53
  # Check if the JSON library is available
54
54
  def available?
55
55
  return @available if defined?(@available)
@@ -39,8 +39,6 @@ module Serialbench
39
39
  false
40
40
  end
41
41
 
42
- protected
43
-
44
42
  def supports_pretty_print?
45
43
  true
46
44
  end
@@ -53,8 +53,6 @@ module Serialbench
53
53
  false
54
54
  end
55
55
 
56
- protected
57
-
58
56
  def supports_pretty_print?
59
57
  true
60
58
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_json_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Json
8
+ # RapidJSON serializer - Ruby bindings for RapidJSON C++ library
9
+ class RapidjsonSerializer < BaseJsonSerializer
10
+ def available?
11
+ require_library('rapidjson')
12
+ end
13
+
14
+ def name
15
+ 'rapidjson'
16
+ end
17
+
18
+ def version
19
+ require 'rapidjson'
20
+ RapidJSON::VERSION
21
+ rescue StandardError
22
+ 'unknown'
23
+ end
24
+
25
+ def parse(json_string)
26
+ require 'rapidjson'
27
+ RapidJSON.parse(json_string)
28
+ end
29
+
30
+ def generate(object, options = {})
31
+ require 'rapidjson'
32
+ RapidJSON.dump(object)
33
+ end
34
+
35
+ def features
36
+ %w[parsing generation high-performance c-extension]
37
+ end
38
+
39
+ private
40
+
41
+ def require_library(library_name)
42
+ require library_name
43
+ true
44
+ rescue LoadError
45
+ false
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -15,9 +15,13 @@ module Serialbench
15
15
  Yajl::Parser.parse(json_string)
16
16
  end
17
17
 
18
- def generate(data)
18
+ def generate(data, options = {})
19
19
  require 'yajl'
20
- Yajl::Encoder.encode(data)
20
+ if options[:pretty]
21
+ Yajl::Encoder.encode(data, pretty: true, indent: ' ')
22
+ else
23
+ Yajl::Encoder.encode(data)
24
+ end
21
25
  end
22
26
 
23
27
  def parse_streaming(json_string, &block)
@@ -56,8 +60,6 @@ module Serialbench
56
60
  end
57
61
  end
58
62
 
59
- protected
60
-
61
63
  def library_require_name
62
64
  'yajl'
63
65
  end
@@ -6,7 +6,7 @@ module Serialbench
6
6
  module Serializers
7
7
  module Toml
8
8
  class BaseTomlSerializer < BaseSerializer
9
- def format
9
+ def self.format
10
10
  :toml
11
11
  end
12
12
 
@@ -29,13 +29,15 @@ module Serialbench
29
29
  }
30
30
  end
31
31
 
32
+ def supports_generation?
33
+ true
34
+ end
35
+
32
36
  def supports_streaming?
33
37
  # TOML is typically not streamed due to its structure
34
38
  false
35
39
  end
36
40
 
37
- protected
38
-
39
41
  def supports_comments?
40
42
  false
41
43
  end
@@ -32,8 +32,6 @@ module Serialbench
32
32
  TomlRB.dump(object)
33
33
  end
34
34
 
35
- protected
36
-
37
35
  def supports_comments?
38
36
  false
39
37
  end
@@ -39,8 +39,6 @@ module Serialbench
39
39
  Tomlib::VERSION
40
40
  end
41
41
 
42
- protected
43
-
44
42
  def library_require_name
45
43
  'tomlib'
46
44
  end