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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +13 -5
  3. data/.github/workflows/docker.yml +35 -9
  4. data/.github/workflows/rake.yml +15 -0
  5. data/Gemfile +2 -1
  6. data/README.adoc +267 -1129
  7. data/Rakefile +0 -55
  8. data/config/benchmarks/full.yml +29 -0
  9. data/config/benchmarks/short.yml +26 -0
  10. data/config/environments/asdf-ruby-3.2.yml +8 -0
  11. data/config/environments/asdf-ruby-3.3.yml +8 -0
  12. data/config/environments/docker-ruby-3.0.yml +9 -0
  13. data/config/environments/docker-ruby-3.1.yml +9 -0
  14. data/config/environments/docker-ruby-3.2.yml +9 -0
  15. data/config/environments/docker-ruby-3.3.yml +9 -0
  16. data/config/environments/docker-ruby-3.4.yml +9 -0
  17. data/docker/Dockerfile.alpine +33 -0
  18. data/docker/{Dockerfile.benchmark → Dockerfile.ubuntu} +4 -3
  19. data/docker/README.md +2 -2
  20. data/exe/serialbench +1 -1
  21. data/lib/serialbench/benchmark_runner.rb +261 -423
  22. data/lib/serialbench/cli/base_cli.rb +51 -0
  23. data/lib/serialbench/cli/benchmark_cli.rb +380 -0
  24. data/lib/serialbench/cli/environment_cli.rb +181 -0
  25. data/lib/serialbench/cli/resultset_cli.rb +215 -0
  26. data/lib/serialbench/cli/ruby_build_cli.rb +238 -0
  27. data/lib/serialbench/cli.rb +58 -601
  28. data/lib/serialbench/config_manager.rb +140 -0
  29. data/lib/serialbench/models/benchmark_config.rb +63 -0
  30. data/lib/serialbench/models/benchmark_result.rb +45 -0
  31. data/lib/serialbench/models/environment_config.rb +71 -0
  32. data/lib/serialbench/models/platform.rb +59 -0
  33. data/lib/serialbench/models/result.rb +53 -0
  34. data/lib/serialbench/models/result_set.rb +71 -0
  35. data/lib/serialbench/models/result_store.rb +108 -0
  36. data/lib/serialbench/models.rb +54 -0
  37. data/lib/serialbench/ruby_build_manager.rb +153 -0
  38. data/lib/serialbench/runners/asdf_runner.rb +296 -0
  39. data/lib/serialbench/runners/base.rb +32 -0
  40. data/lib/serialbench/runners/docker_runner.rb +142 -0
  41. data/lib/serialbench/serializers/base_serializer.rb +8 -16
  42. data/lib/serialbench/serializers/json/base_json_serializer.rb +4 -4
  43. data/lib/serialbench/serializers/json/json_serializer.rb +0 -2
  44. data/lib/serialbench/serializers/json/oj_serializer.rb +0 -2
  45. data/lib/serialbench/serializers/json/yajl_serializer.rb +0 -2
  46. data/lib/serialbench/serializers/toml/base_toml_serializer.rb +5 -3
  47. data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +0 -2
  48. data/lib/serialbench/serializers/toml/tomlib_serializer.rb +0 -2
  49. data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +56 -0
  50. data/lib/serialbench/serializers/xml/base_xml_serializer.rb +4 -9
  51. data/lib/serialbench/serializers/xml/libxml_serializer.rb +0 -2
  52. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +0 -2
  53. data/lib/serialbench/serializers/xml/oga_serializer.rb +0 -2
  54. data/lib/serialbench/serializers/xml/ox_serializer.rb +0 -2
  55. data/lib/serialbench/serializers/xml/rexml_serializer.rb +0 -2
  56. data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +5 -1
  57. data/lib/serialbench/serializers/yaml/syck_serializer.rb +59 -22
  58. data/lib/serialbench/serializers.rb +23 -6
  59. data/lib/serialbench/site_generator.rb +105 -0
  60. data/lib/serialbench/templates/assets/css/benchmark_report.css +535 -0
  61. data/lib/serialbench/templates/assets/css/format_based.css +526 -0
  62. data/lib/serialbench/templates/assets/css/themes.css +588 -0
  63. data/lib/serialbench/templates/assets/js/chart_helpers.js +381 -0
  64. data/lib/serialbench/templates/assets/js/dashboard.js +796 -0
  65. data/lib/serialbench/templates/assets/js/navigation.js +142 -0
  66. data/lib/serialbench/templates/base.liquid +49 -0
  67. data/lib/serialbench/templates/format_based.liquid +279 -0
  68. data/lib/serialbench/templates/partials/chart_section.liquid +4 -0
  69. data/lib/serialbench/version.rb +1 -1
  70. data/lib/serialbench.rb +2 -31
  71. data/serialbench.gemspec +4 -1
  72. metadata +86 -16
  73. data/config/ci.yml +0 -22
  74. data/config/full.yml +0 -30
  75. data/docker/run-benchmarks.sh +0 -356
  76. data/lib/serialbench/chart_generator.rb +0 -821
  77. data/lib/serialbench/result_formatter.rb +0 -182
  78. data/lib/serialbench/result_merger.rb +0 -1201
  79. data/lib/serialbench/serializers/xml/base_parser.rb +0 -69
  80. data/lib/serialbench/serializers/xml/libxml_parser.rb +0 -98
  81. data/lib/serialbench/serializers/xml/nokogiri_parser.rb +0 -111
  82. data/lib/serialbench/serializers/xml/oga_parser.rb +0 -85
  83. data/lib/serialbench/serializers/xml/ox_parser.rb +0 -64
  84. data/lib/serialbench/serializers/xml/rexml_parser.rb +0 -129
@@ -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
@@ -60,8 +60,6 @@ module Serialbench
60
60
  end
61
61
  end
62
62
 
63
- protected
64
-
65
63
  def library_require_name
66
64
  'yajl'
67
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
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_toml_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Toml
8
+ class TomlrbSerializer < BaseTomlSerializer
9
+ def available?
10
+ require_library('tomlrb')
11
+ end
12
+
13
+ def name
14
+ 'tomlrb'
15
+ end
16
+
17
+ def version
18
+ require 'tomlrb'
19
+ # tomlrb doesn't expose a VERSION constant, so we'll use gem version
20
+ Gem.loaded_specs['tomlrb']&.version&.to_s || 'unknown'
21
+ rescue LoadError, NameError
22
+ 'unknown'
23
+ end
24
+
25
+ def parse(toml_string)
26
+ require 'tomlrb'
27
+ Tomlrb.parse(toml_string)
28
+ end
29
+
30
+ def generate(object, options = {})
31
+ raise NotImplementedError, 'tomlrb gem does not support TOML generation/dumping'
32
+ end
33
+
34
+ def supports_generation?
35
+ false
36
+ end
37
+
38
+ def supports_comments?
39
+ false
40
+ end
41
+
42
+ def supports_arrays_of_tables?
43
+ true
44
+ end
45
+
46
+ def supports_inline_tables?
47
+ true
48
+ end
49
+
50
+ def supports_multiline_strings?
51
+ true
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -6,7 +6,7 @@ module Serialbench
6
6
  module Serializers
7
7
  module Xml
8
8
  class BaseXmlSerializer < BaseSerializer
9
- def format
9
+ def self.format
10
10
  :xml
11
11
  end
12
12
 
@@ -33,7 +33,9 @@ module Serialbench
33
33
  }
34
34
  end
35
35
 
36
- protected
36
+ def supports_generation?
37
+ true
38
+ end
37
39
 
38
40
  def supports_xpath?
39
41
  false
@@ -47,13 +49,6 @@ module Serialbench
47
49
  false
48
50
  end
49
51
 
50
- # Subclasses should override this to specify their library name
51
- def library_require_name
52
- raise NotImplementedError, 'Subclasses must implement #library_require_name'
53
- end
54
-
55
- public
56
-
57
52
  # Check if the XML library is available
58
53
  def available?
59
54
  return @available if defined?(@available)
@@ -49,8 +49,6 @@ module Serialbench
49
49
  LibXML::XML::VERSION
50
50
  end
51
51
 
52
- protected
53
-
54
52
  def library_require_name
55
53
  'libxml'
56
54
  end
@@ -49,8 +49,6 @@ module Serialbench
49
49
  Nokogiri::VERSION
50
50
  end
51
51
 
52
- protected
53
-
54
52
  def library_require_name
55
53
  'nokogiri'
56
54
  end
@@ -49,8 +49,6 @@ module Serialbench
49
49
  Oga::VERSION
50
50
  end
51
51
 
52
- protected
53
-
54
52
  def library_require_name
55
53
  'oga'
56
54
  end
@@ -40,8 +40,6 @@ module Serialbench
40
40
  Ox::VERSION
41
41
  end
42
42
 
43
- protected
44
-
45
43
  def library_require_name
46
44
  'ox'
47
45
  end
@@ -77,8 +77,6 @@ module Serialbench
77
77
  false
78
78
  end
79
79
 
80
- protected
81
-
82
80
  def supports_xpath?
83
81
  true
84
82
  end
@@ -7,7 +7,7 @@ module Serialbench
7
7
  module Yaml
8
8
  # Base class for YAML serializers
9
9
  class BaseYamlSerializer < BaseSerializer
10
- def format
10
+ def self.format
11
11
  :yaml
12
12
  end
13
13
 
@@ -41,6 +41,10 @@ module Serialbench
41
41
  raise NotImplementedError, 'Streaming not supported by this YAML serializer'
42
42
  end
43
43
 
44
+ def supports_generation?
45
+ true
46
+ end
47
+
44
48
  private
45
49
 
46
50
  def require_library(library_name)
@@ -11,41 +11,61 @@ module Serialbench
11
11
  end
12
12
 
13
13
  def version
14
- return 'N/A' unless available?
14
+ require 'syck'
15
+ # Try to get version from gem specification
16
+ spec = Gem.loaded_specs['syck']
17
+ return spec.version.to_s if spec
15
18
 
16
- begin
17
- require 'syck'
18
- # Try to get version from gem specification
19
- spec = Gem.loaded_specs['syck']
20
- return spec.version.to_s if spec
21
-
22
- # Fallback to a default version if no gem spec found
23
- '1.0.0'
24
- rescue
25
- 'N/A'
26
- end
19
+ # Fallback to a default version if no gem spec found
20
+ '1.0.0'
21
+ rescue StandardError
22
+ 'N/A'
27
23
  end
28
24
 
29
25
  def available?
30
- begin
31
- require 'syck'
32
- # Verify that Syck module and methods are actually available
33
- defined?(Syck) && Syck.respond_to?(:dump) && Syck.respond_to?(:load)
34
- rescue LoadError
35
- false
26
+ require 'syck'
27
+ # Verify that Syck module and methods are actually available
28
+ return false unless defined?(Syck) && Syck.respond_to?(:dump) && Syck.respond_to?(:load)
29
+
30
+ # Check for known problematic configurations
31
+ if problematic_environment?
32
+ warn_about_segfault_issue
33
+ return false
36
34
  end
35
+
36
+ true
37
+ rescue LoadError
38
+ false
37
39
  end
38
40
 
39
41
  def parse(yaml_string)
40
42
  return nil unless available?
41
- require 'syck'
42
- Syck.load(yaml_string)
43
+
44
+ begin
45
+ require 'syck'
46
+ Syck.load(yaml_string)
47
+ rescue StandardError => e
48
+ if e.message.include?('Segmentation fault') || e.is_a?(SystemExit)
49
+ handle_segfault_error
50
+ return nil
51
+ end
52
+ raise e
53
+ end
43
54
  end
44
55
 
45
56
  def generate(object, options = {})
46
57
  return nil unless available?
47
- require 'syck'
48
- Syck.dump(object)
58
+
59
+ begin
60
+ require 'syck'
61
+ Syck.dump(object)
62
+ rescue StandardError => e
63
+ if e.message.include?('Segmentation fault') || e.is_a?(SystemExit)
64
+ handle_segfault_error
65
+ return nil
66
+ end
67
+ raise e
68
+ end
49
69
  end
50
70
 
51
71
  def supports_streaming?
@@ -59,6 +79,23 @@ module Serialbench
59
79
  def description
60
80
  'Legacy YAML parser (Ruby < 1.9.3)'
61
81
  end
82
+
83
+ def problematic_environment?
84
+ # Ruby 3.1+ has issues with Syck
85
+ (Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.1.0')) &&
86
+ (Gem::Version.new(version) >= Gem::Version.new('1.4.0'))
87
+ end
88
+
89
+ def warn_about_segfault_issue
90
+ puts "⚠️ WARNING: The Syck YAML serializer is known to cause segmentation faults with Ruby #{RUBY_VERSION}"
91
+ puts ' This is a known issue with the syck gem on newer Ruby versions systems.'
92
+ puts ' Skipping Syck benchmarks to prevent crashes. See README for more details.'
93
+ end
94
+
95
+ def handle_segfault_error
96
+ puts '❌ ERROR: Syck YAML serializer encountered a segmentation fault'
97
+ puts " This is a known issue on Ruby #{RUBY_VERSION}. Skipping remaining Syck tests."
98
+ end
62
99
  end
63
100
  end
64
101
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'serializers/base_serializer'
4
+ require_relative 'models/benchmark_result'
4
5
 
5
6
  # XML Serializers
6
7
  require_relative 'serializers/xml/base_xml_serializer'
@@ -26,11 +27,12 @@ require_relative 'serializers/yaml/syck_serializer'
26
27
  require_relative 'serializers/toml/base_toml_serializer'
27
28
  require_relative 'serializers/toml/toml_rb_serializer'
28
29
  require_relative 'serializers/toml/tomlib_serializer'
30
+ require_relative 'serializers/toml/tomlrb_serializer'
29
31
 
30
32
  module Serialbench
31
33
  module Serializers
32
34
  # Registry of all available serializers
33
- SERIALIZERS = {
35
+ REGISTER = {
34
36
  xml: [
35
37
  Xml::RexmlSerializer,
36
38
  Xml::OxSerializer,
@@ -50,24 +52,39 @@ module Serialbench
50
52
  ],
51
53
  toml: [
52
54
  Toml::TomlRbSerializer,
53
- Toml::TomlibSerializer
55
+ Toml::TomlibSerializer,
56
+ Toml::TomlrbSerializer
54
57
  ]
55
58
  }.freeze
56
59
 
57
60
  def self.all
58
- SERIALIZERS.values.flatten
61
+ REGISTER.values.flatten.map(&:instance)
59
62
  end
60
63
 
61
64
  def self.for_format(format)
62
- SERIALIZERS[format.to_sym] || []
65
+ REGISTER[format.to_sym]&.map(&:instance) || []
66
+ end
67
+
68
+ def self.information
69
+ return @information if @information
70
+
71
+ @information = available.map do |serializer_singleton|
72
+ Models::SerializerInformation.new(
73
+ name: serializer_singleton.name,
74
+ format: serializer_singleton.format.to_s,
75
+ version: serializer_singleton.version
76
+ )
77
+ end
78
+
79
+ @information
63
80
  end
64
81
 
65
82
  def self.available_for_format(format)
66
- for_format(format).select { |serializer_class| serializer_class.new.available? }
83
+ for_format(format).select { |serializer_singleton| serializer_singleton.available? }
67
84
  end
68
85
 
69
86
  def self.available
70
- all.select { |serializer_class| serializer_class.new.available? }
87
+ all.select { |serializer_singleton| serializer_singleton.available? }
71
88
  end
72
89
  end
73
90
  end
@@ -0,0 +1,105 @@
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 generate_site
34
+ target_name = @result ? @result.environment_config.name : @resultset.name
35
+ data = @result ? @result.to_json : @resultset.to_json
36
+
37
+ puts "🏗️ Generating HTML site for #{@result ? 'run' : 'resultset'}: #{target_name}"
38
+ puts "Output: #{@output_path}"
39
+
40
+ prepare_output_directory
41
+ render_site(
42
+ {
43
+ 'data' => data,
44
+ 'kind' => @result ? 'run' : 'resultset'
45
+ },
46
+ 'format_based.liquid'
47
+ )
48
+
49
+ puts "✅ Site generated successfully at: #{@output_path}"
50
+ @output_path
51
+ end
52
+
53
+ private
54
+
55
+ def setup_liquid_environment
56
+ @liquid_env = Liquid::Environment.new
57
+ @liquid_env.file_system = Liquid::LocalFileSystem.new(TEMPLATE_DIR)
58
+ end
59
+
60
+ def prepare_output_directory
61
+ if Dir.exist?(@output_path)
62
+ puts 'Cleaning existing output directory...'
63
+ FileUtils.rm_rf(Dir.glob(File.join(@output_path, '*')))
64
+ else
65
+ FileUtils.mkdir_p(@output_path)
66
+ end
67
+ end
68
+
69
+ def render_site(template_data, template_name)
70
+ # Load and render content template
71
+ content_template = load_template(template_name)
72
+ content = content_template.render(template_data)
73
+
74
+ # Load and render base template
75
+ base_template = load_template('base.liquid')
76
+ html = base_template.render(template_data.merge('content' => content))
77
+
78
+ # Write HTML file
79
+ write_file(html, 'index.html')
80
+
81
+ # Copy assets
82
+ copy_assets
83
+ end
84
+
85
+ def load_template(template_name)
86
+ template_path = File.join(TEMPLATE_DIR, template_name)
87
+ template_content = File.read(template_path)
88
+ Liquid::Template.parse(template_content)
89
+ end
90
+
91
+ def write_file(content, filename)
92
+ FileUtils.mkdir_p(@output_path)
93
+ File.write(File.join(@output_path, filename), content)
94
+ end
95
+
96
+ def copy_assets
97
+ assets_source = File.join(TEMPLATE_DIR, 'assets')
98
+ assets_dest = File.join(@output_path, 'assets')
99
+
100
+ return unless Dir.exist?(assets_source)
101
+
102
+ FileUtils.cp_r(assets_source, assets_dest)
103
+ end
104
+ end
105
+ end