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
@@ -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
|
@@ -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
|
-
|
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)
|
@@ -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
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
42
|
-
|
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
|
-
|
48
|
-
|
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
|
-
|
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
|
-
|
61
|
+
REGISTER.values.flatten.map(&:instance)
|
59
62
|
end
|
60
63
|
|
61
64
|
def self.for_format(format)
|
62
|
-
|
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 { |
|
83
|
+
for_format(format).select { |serializer_singleton| serializer_singleton.available? }
|
67
84
|
end
|
68
85
|
|
69
86
|
def self.available
|
70
|
-
all.select { |
|
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
|