serialbench 0.1.2 → 0.1.3
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 +273 -228
- data/.github/workflows/rake.yml +11 -0
- data/.github/workflows/windows-debug.yml +171 -0
- data/.gitignore +32 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +274 -0
- data/Gemfile +13 -1
- data/README.adoc +36 -0
- data/data/schemas/result.yml +29 -0
- data/docs/PLATFORM_VALIDATION_FIX.md +79 -0
- data/docs/SYCK_YAML_FIX.md +91 -0
- data/docs/WEBSITE_COMPLETION_PLAN.md +440 -0
- data/docs/WINDOWS_LIBXML_FIX.md +136 -0
- data/docs/WINDOWS_SETUP.md +122 -0
- data/lib/serialbench/benchmark_runner.rb +3 -3
- data/lib/serialbench/cli/benchmark_cli.rb +74 -1
- data/lib/serialbench/cli/environment_cli.rb +3 -3
- data/lib/serialbench/cli/resultset_cli.rb +72 -26
- data/lib/serialbench/cli/ruby_build_cli.rb +75 -88
- data/lib/serialbench/cli/validate_cli.rb +88 -0
- data/lib/serialbench/cli.rb +6 -2
- data/lib/serialbench/config_manager.rb +15 -26
- data/lib/serialbench/models/benchmark_config.rb +12 -0
- data/lib/serialbench/models/benchmark_result.rb +39 -3
- data/lib/serialbench/models/environment_config.rb +3 -2
- data/lib/serialbench/models/platform.rb +56 -4
- data/lib/serialbench/models/result.rb +28 -1
- data/lib/serialbench/models/result_set.rb +8 -0
- data/lib/serialbench/ruby_build_manager.rb +19 -23
- data/lib/serialbench/runners/asdf_runner.rb +1 -1
- data/lib/serialbench/runners/docker_runner.rb +2 -4
- data/lib/serialbench/runners/local_runner.rb +71 -0
- data/lib/serialbench/serializers/base_serializer.rb +1 -1
- data/lib/serialbench/serializers/json/rapidjson_serializer.rb +1 -1
- data/lib/serialbench/serializers/toml/base_toml_serializer.rb +0 -2
- data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +1 -1
- data/lib/serialbench/serializers/toml/tomlib_serializer.rb +1 -1
- data/lib/serialbench/serializers/xml/libxml_serializer.rb +4 -8
- data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +2 -2
- data/lib/serialbench/serializers/xml/oga_serializer.rb +4 -8
- data/lib/serialbench/serializers/xml/ox_serializer.rb +2 -2
- data/lib/serialbench/serializers/xml/rexml_serializer.rb +3 -3
- data/lib/serialbench/serializers/yaml/psych_serializer.rb +1 -1
- data/lib/serialbench/serializers/yaml/syck_serializer.rb +1 -1
- data/lib/serialbench/serializers.rb +2 -2
- data/lib/serialbench/site_generator.rb +180 -2
- data/lib/serialbench/templates/assets/css/format_based.css +1 -53
- data/lib/serialbench/templates/assets/css/themes.css +5 -4
- data/lib/serialbench/templates/assets/js/chart_helpers.js +44 -14
- data/lib/serialbench/templates/assets/js/dashboard.js +14 -15
- data/lib/serialbench/templates/format_based.liquid +480 -252
- data/lib/serialbench/version.rb +1 -1
- data/lib/serialbench/yaml_validator.rb +36 -0
- data/serialbench.gemspec +11 -2
- metadata +34 -23
- data/.github/workflows/ci.yml +0 -74
- data/.github/workflows/docker.yml +0 -272
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_cli'
|
|
4
|
+
require_relative '../yaml_validator'
|
|
5
|
+
|
|
6
|
+
module Serialbench
|
|
7
|
+
module Cli
|
|
8
|
+
# CLI for validating YAML files against schemas
|
|
9
|
+
class ValidateCli < BaseCli
|
|
10
|
+
desc 'result RESULT_FILE', 'Validate a benchmark result YAML file'
|
|
11
|
+
long_desc <<~DESC
|
|
12
|
+
Validate a benchmark result YAML file against its schema.
|
|
13
|
+
|
|
14
|
+
RESULT_FILE should be the path to a results.yaml file
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
serialbench validate result results/runs/my-run/results.yaml
|
|
18
|
+
serialbench validate result test-artifacts/benchmark-results-ubuntu-latest-ruby-3.4/results.yaml
|
|
19
|
+
DESC
|
|
20
|
+
def result(file_path)
|
|
21
|
+
validate_file(file_path, 'result', 'Result')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc 'config BENCHMARK_CONFIG', 'Validate a benchmark configuration file'
|
|
25
|
+
long_desc <<~DESC
|
|
26
|
+
Validate a benchmark configuration file against its schema.
|
|
27
|
+
|
|
28
|
+
BENCHMARK_CONFIG should be the path to a benchmark config YAML file
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
serialbench validate config config/benchmarks/short.yml
|
|
32
|
+
serialbench validate config config/benchmarks/full.yml
|
|
33
|
+
DESC
|
|
34
|
+
def config(file_path)
|
|
35
|
+
validate_file(file_path, 'benchmark_config', 'Benchmark config')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc 'environment ENV_CONFIG', 'Validate an environment configuration file'
|
|
39
|
+
long_desc <<~DESC
|
|
40
|
+
Validate an environment configuration file against its schema.
|
|
41
|
+
|
|
42
|
+
ENV_CONFIG should be the path to an environment config YAML file
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
serialbench validate environment config/environments/local-dev.yml
|
|
46
|
+
serialbench validate environment config/environments/docker-ruby-3.4.yml
|
|
47
|
+
DESC
|
|
48
|
+
def environment(file_path)
|
|
49
|
+
validate_file(file_path, 'environment_config', 'Environment config')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc 'resultset RESULTSET_FILE', 'Validate a resultset YAML file'
|
|
53
|
+
long_desc <<~DESC
|
|
54
|
+
Validate a resultset YAML file against its schema.
|
|
55
|
+
|
|
56
|
+
RESULTSET_FILE should be the path to a resultset.yaml file
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
serialbench validate resultset results/sets/weekly-benchmark/resultset.yaml
|
|
60
|
+
serialbench validate resultset results/sets/comparison/resultset.yaml
|
|
61
|
+
DESC
|
|
62
|
+
def resultset(file_path)
|
|
63
|
+
validate_file(file_path, 'resultset', 'Resultset')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def validate_file(file_path, schema_name, friendly_name)
|
|
69
|
+
unless File.exist?(file_path)
|
|
70
|
+
say "❌ File not found: #{file_path}", :red
|
|
71
|
+
exit 1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
say "📝 Validating #{friendly_name}: #{file_path}", :cyan
|
|
75
|
+
|
|
76
|
+
if Serialbench::YamlValidator.validate(file_path, schema_name)
|
|
77
|
+
say "✅ #{friendly_name} is valid", :green
|
|
78
|
+
else
|
|
79
|
+
say "❌ #{friendly_name} validation failed", :red
|
|
80
|
+
exit 1
|
|
81
|
+
end
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
say "❌ Error: #{e.message}", :red
|
|
84
|
+
exit 1
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/serialbench/cli.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative 'cli/environment_cli'
|
|
|
6
6
|
require_relative 'cli/benchmark_cli'
|
|
7
7
|
require_relative 'cli/resultset_cli'
|
|
8
8
|
require_relative 'cli/ruby_build_cli'
|
|
9
|
+
require_relative 'cli/validate_cli'
|
|
9
10
|
|
|
10
11
|
module Serialbench
|
|
11
12
|
# Main CLI entry point for the new object-oriented command structure
|
|
@@ -22,6 +23,9 @@ module Serialbench
|
|
|
22
23
|
desc 'ruby_build SUBCOMMAND', 'Manage Ruby-Build definitions for validation'
|
|
23
24
|
subcommand 'ruby_build', Serialbench::Cli::RubyBuildCli
|
|
24
25
|
|
|
26
|
+
desc 'validate SUBCOMMAND', 'Validate YAML files against schemas'
|
|
27
|
+
subcommand 'validate', Serialbench::Cli::ValidateCli
|
|
28
|
+
|
|
25
29
|
desc 'version', 'Show Serialbench version'
|
|
26
30
|
def self.version
|
|
27
31
|
puts "Serialbench version #{Serialbench::VERSION}"
|
|
@@ -73,14 +77,14 @@ module Serialbench
|
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
# Handle unknown commands gracefully
|
|
76
|
-
def method_missing(method_name, *
|
|
80
|
+
def method_missing(method_name, *_args)
|
|
77
81
|
puts "Unknown command: #{method_name}"
|
|
78
82
|
puts ''
|
|
79
83
|
help
|
|
80
84
|
exit 1
|
|
81
85
|
end
|
|
82
86
|
|
|
83
|
-
def respond_to_missing?(
|
|
87
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
|
84
88
|
false
|
|
85
89
|
end
|
|
86
90
|
end
|
|
@@ -39,16 +39,14 @@ module Serialbench
|
|
|
39
39
|
private
|
|
40
40
|
|
|
41
41
|
# Validate configuration against schema
|
|
42
|
-
def validate_config(config_data,
|
|
42
|
+
def validate_config(config_data, _config_path)
|
|
43
43
|
# For now, perform basic validation since we don't have a specific config schema validator
|
|
44
44
|
validate_basic_config(config_data)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# Load the configuration schema
|
|
48
48
|
def load_schema
|
|
49
|
-
unless File.exist?(SCHEMA_PATH)
|
|
50
|
-
raise ConfigurationError, "Configuration schema not found: #{SCHEMA_PATH}"
|
|
51
|
-
end
|
|
49
|
+
raise ConfigurationError, "Configuration schema not found: #{SCHEMA_PATH}" unless File.exist?(SCHEMA_PATH)
|
|
52
50
|
|
|
53
51
|
begin
|
|
54
52
|
YAML.load_file(SCHEMA_PATH)
|
|
@@ -81,28 +79,22 @@ module Serialbench
|
|
|
81
79
|
|
|
82
80
|
# Validate Docker-specific configuration
|
|
83
81
|
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
|
|
82
|
+
raise ConfigurationError, "Docker runtime requires 'image_variants' to be specified" unless config.image_variants && !config.image_variants.empty?
|
|
87
83
|
|
|
88
84
|
invalid_variants = config.image_variants - %w[slim alpine]
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
return if invalid_variants.empty?
|
|
86
|
+
|
|
87
|
+
raise ConfigurationError, "Invalid image variants: #{invalid_variants.join(', ')}. Valid variants: slim, alpine"
|
|
92
88
|
end
|
|
93
89
|
|
|
94
90
|
# Validate ASDF-specific configuration
|
|
95
91
|
def validate_asdf_config(config)
|
|
96
92
|
# 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
|
|
93
|
+
raise ConfigurationError, 'ASDF is not installed or not in PATH. Please install ASDF to use asdf runtime.' unless command_available?('asdf')
|
|
100
94
|
|
|
101
95
|
# Validate Ruby version format for ASDF (should include patch version)
|
|
102
96
|
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
|
|
97
|
+
raise ConfigurationError, "ASDF runtime requires full version numbers (e.g., '3.2.8'), got: #{version}" unless version.match?(/^\d+\.\d+\.\d+$/)
|
|
106
98
|
end
|
|
107
99
|
end
|
|
108
100
|
|
|
@@ -111,25 +103,22 @@ module Serialbench
|
|
|
111
103
|
# Check required fields
|
|
112
104
|
required_fields = %w[runtime ruby_versions output_dir benchmark_config]
|
|
113
105
|
required_fields.each do |field|
|
|
114
|
-
unless config_data.key?(field)
|
|
115
|
-
raise ConfigurationError, "Missing required field: #{field}"
|
|
116
|
-
end
|
|
106
|
+
raise ConfigurationError, "Missing required field: #{field}" unless config_data.key?(field)
|
|
117
107
|
end
|
|
118
108
|
|
|
119
109
|
# Validate runtime
|
|
120
110
|
valid_runtimes = %w[docker asdf]
|
|
121
111
|
unless valid_runtimes.include?(config_data['runtime'])
|
|
122
|
-
raise ConfigurationError,
|
|
112
|
+
raise ConfigurationError,
|
|
113
|
+
"Invalid runtime: #{config_data['runtime']}. Valid runtimes: #{valid_runtimes.join(', ')}"
|
|
123
114
|
end
|
|
124
115
|
|
|
125
116
|
# 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
|
|
117
|
+
raise ConfigurationError, 'ruby_versions must be an array' unless config_data['ruby_versions'].is_a?(Array)
|
|
129
118
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
119
|
+
return unless config_data['ruby_versions'].empty?
|
|
120
|
+
|
|
121
|
+
raise ConfigurationError, 'ruby_versions cannot be empty'
|
|
133
122
|
end
|
|
134
123
|
|
|
135
124
|
# Check if a command is available in PATH
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'lutaml/model'
|
|
2
4
|
|
|
3
5
|
module Serialbench
|
|
@@ -51,6 +53,16 @@ module Serialbench
|
|
|
51
53
|
attribute :warmup, :integer, default: -> { 1 }
|
|
52
54
|
attribute :operations, :string, collection: true, values: %w[parse generate memory streaming]
|
|
53
55
|
|
|
56
|
+
key_value do
|
|
57
|
+
map 'name', to: :name
|
|
58
|
+
map 'description', to: :description
|
|
59
|
+
map 'data_sizes', to: :data_sizes
|
|
60
|
+
map 'formats', to: :formats
|
|
61
|
+
map 'iterations', to: :iterations
|
|
62
|
+
map 'warmup', to: :warmup
|
|
63
|
+
map 'operations', to: :operations
|
|
64
|
+
end
|
|
65
|
+
|
|
54
66
|
def to_file(file_path)
|
|
55
67
|
File.write(file_path, to_yaml)
|
|
56
68
|
end
|
|
@@ -8,16 +8,24 @@ module Serialbench
|
|
|
8
8
|
attribute :format, :string, values: %w[xml json yaml toml]
|
|
9
9
|
attribute :name, :string
|
|
10
10
|
attribute :version, :string
|
|
11
|
-
end
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
key_value do
|
|
13
|
+
map 'format', to: :format
|
|
14
|
+
map 'name', to: :name
|
|
15
|
+
map 'version', to: :version
|
|
16
|
+
end
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
class AdapterPerformance < Lutaml::Model::Serializable
|
|
18
20
|
attribute :adapter, :string
|
|
19
21
|
attribute :format, :string, values: %w[xml json yaml toml]
|
|
20
22
|
attribute :data_size, :string, values: %w[small medium large]
|
|
23
|
+
|
|
24
|
+
key_value do
|
|
25
|
+
map 'adapter', to: :adapter
|
|
26
|
+
map 'format', to: :format
|
|
27
|
+
map 'data_size', to: :data_size
|
|
28
|
+
end
|
|
21
29
|
end
|
|
22
30
|
|
|
23
31
|
class IterationPerformance < AdapterPerformance
|
|
@@ -25,6 +33,16 @@ module Serialbench
|
|
|
25
33
|
attribute :time_per_iteration, :float
|
|
26
34
|
attribute :iterations_per_second, :float
|
|
27
35
|
attribute :iterations_count, :integer
|
|
36
|
+
|
|
37
|
+
key_value do
|
|
38
|
+
map 'adapter', to: :adapter
|
|
39
|
+
map 'format', to: :format
|
|
40
|
+
map 'data_size', to: :data_size
|
|
41
|
+
map 'time_per_iterations', to: :time_per_iterations
|
|
42
|
+
map 'time_per_iteration', to: :time_per_iteration
|
|
43
|
+
map 'iterations_per_second', to: :iterations_per_second
|
|
44
|
+
map 'iterations_count', to: :iterations_count
|
|
45
|
+
end
|
|
28
46
|
end
|
|
29
47
|
|
|
30
48
|
class MemoryPerformance < AdapterPerformance
|
|
@@ -32,6 +50,16 @@ module Serialbench
|
|
|
32
50
|
attribute :total_retained, :integer
|
|
33
51
|
attribute :allocated_memory, :integer
|
|
34
52
|
attribute :retained_memory, :integer
|
|
53
|
+
|
|
54
|
+
key_value do
|
|
55
|
+
map 'adapter', to: :adapter
|
|
56
|
+
map 'format', to: :format
|
|
57
|
+
map 'data_size', to: :data_size
|
|
58
|
+
map 'total_allocated', to: :total_allocated
|
|
59
|
+
map 'total_retained', to: :total_retained
|
|
60
|
+
map 'allocated_memory', to: :allocated_memory
|
|
61
|
+
map 'retained_memory', to: :retained_memory
|
|
62
|
+
end
|
|
35
63
|
end
|
|
36
64
|
|
|
37
65
|
class BenchmarkResult < Lutaml::Model::Serializable
|
|
@@ -40,6 +68,14 @@ module Serialbench
|
|
|
40
68
|
attribute :generation, IterationPerformance, collection: true
|
|
41
69
|
attribute :memory, MemoryPerformance, collection: true
|
|
42
70
|
attribute :streaming, IterationPerformance, collection: true
|
|
71
|
+
|
|
72
|
+
key_value do
|
|
73
|
+
map 'serializers', to: :serializers
|
|
74
|
+
map 'parsing', to: :parsing
|
|
75
|
+
map 'generation', to: :generation
|
|
76
|
+
map 'memory', to: :memory
|
|
77
|
+
map 'streaming', to: :streaming
|
|
78
|
+
end
|
|
43
79
|
end
|
|
44
80
|
end
|
|
45
81
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'lutaml/model'
|
|
2
4
|
require_relative '../ruby_build_manager'
|
|
3
5
|
|
|
@@ -43,8 +45,7 @@ module Serialbench
|
|
|
43
45
|
attribute :name, :string
|
|
44
46
|
attribute :kind, :string
|
|
45
47
|
attribute :created_at, :string, default: -> { Time.now.utc.iso8601 }
|
|
46
|
-
attribute :ruby_build_tag, :string
|
|
47
|
-
RubyBuildManager.list_definitions
|
|
48
|
+
attribute :ruby_build_tag, :string
|
|
48
49
|
attribute :description, :string
|
|
49
50
|
attribute :docker, DockerEnvConfig
|
|
50
51
|
attribute :asdf, AsdfEnvConfig
|
|
@@ -19,13 +19,39 @@ module Serialbench
|
|
|
19
19
|
attribute :arch, :string, default: -> { detect_arch }
|
|
20
20
|
attribute :ruby_version, :string, default: -> { RUBY_VERSION }
|
|
21
21
|
attribute :ruby_platform, :string, default: -> { RUBY_PLATFORM }
|
|
22
|
-
attribute :ruby_build_tag, :string
|
|
22
|
+
attribute :ruby_build_tag, :string
|
|
23
|
+
|
|
24
|
+
key_value do
|
|
25
|
+
map 'platform_string', to: :platform_string
|
|
26
|
+
map 'kind', to: :kind
|
|
27
|
+
map 'os', to: :os
|
|
28
|
+
map 'arch', to: :arch
|
|
29
|
+
map 'ruby_version', to: :ruby_version
|
|
30
|
+
map 'ruby_platform', to: :ruby_platform
|
|
31
|
+
map 'ruby_build_tag', to: :ruby_build_tag
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.current_local(ruby_version: nil)
|
|
35
|
+
version = ruby_version || RUBY_VERSION
|
|
36
|
+
|
|
37
|
+
# Check for GitHub Actions runner platform
|
|
38
|
+
github_platform = ENV['GITHUB_RUNNER_PLATFORM']
|
|
39
|
+
if github_platform
|
|
40
|
+
os, arch = parse_github_platform(github_platform)
|
|
41
|
+
platform_string = "#{github_platform}-ruby-#{version}"
|
|
42
|
+
else
|
|
43
|
+
os = detect_os
|
|
44
|
+
arch = detect_arch
|
|
45
|
+
platform_string = "local-#{version}"
|
|
46
|
+
end
|
|
23
47
|
|
|
24
|
-
def self.current_local
|
|
25
48
|
new(
|
|
26
|
-
platform_string:
|
|
49
|
+
platform_string: platform_string,
|
|
27
50
|
kind: 'local',
|
|
28
|
-
|
|
51
|
+
os: os,
|
|
52
|
+
arch: arch,
|
|
53
|
+
ruby_version: version,
|
|
54
|
+
ruby_build_tag: version
|
|
29
55
|
)
|
|
30
56
|
end
|
|
31
57
|
|
|
@@ -54,6 +80,32 @@ module Serialbench
|
|
|
54
80
|
'unknown'
|
|
55
81
|
end
|
|
56
82
|
end
|
|
83
|
+
|
|
84
|
+
def self.parse_github_platform(platform_name)
|
|
85
|
+
# Parse GitHub Actions runner platform names
|
|
86
|
+
# Examples: ubuntu-24.04, ubuntu-24.04-arm, macos-13, macos-15-intel,
|
|
87
|
+
# macos-14, macos-15, macos-26, windows-2022, windows-2025, windows-11-arm
|
|
88
|
+
|
|
89
|
+
case platform_name
|
|
90
|
+
when /^ubuntu.*-arm$/
|
|
91
|
+
['linux', 'arm64']
|
|
92
|
+
when /^ubuntu/
|
|
93
|
+
['linux', 'x86_64']
|
|
94
|
+
when /^macos-.*-intel$/
|
|
95
|
+
['macos', 'x86_64']
|
|
96
|
+
when /^macos-(13)$/
|
|
97
|
+
['macos', 'x86_64']
|
|
98
|
+
when /^macos-(14|15|26)$/
|
|
99
|
+
['macos', 'arm64']
|
|
100
|
+
when /^windows.*-arm$/
|
|
101
|
+
['windows', 'arm64']
|
|
102
|
+
when /^windows/
|
|
103
|
+
['windows', 'x86_64']
|
|
104
|
+
else
|
|
105
|
+
# Fallback to automatic detection
|
|
106
|
+
[detect_os, detect_arch]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
57
109
|
end
|
|
58
110
|
end
|
|
59
111
|
end
|
|
@@ -14,6 +14,13 @@ module Serialbench
|
|
|
14
14
|
attribute :benchmark_config_path, :string
|
|
15
15
|
attribute :environment_config_path, :string
|
|
16
16
|
attribute :tags, :string, collection: true
|
|
17
|
+
|
|
18
|
+
key_value do
|
|
19
|
+
map 'created_at', to: :created_at
|
|
20
|
+
map 'benchmark_config_path', to: :benchmark_config_path
|
|
21
|
+
map 'environment_config_path', to: :environment_config_path
|
|
22
|
+
map 'tags', to: :tags
|
|
23
|
+
end
|
|
17
24
|
end
|
|
18
25
|
|
|
19
26
|
class Result < Lutaml::Model::Serializable
|
|
@@ -23,6 +30,14 @@ module Serialbench
|
|
|
23
30
|
attribute :benchmark_config, BenchmarkConfig
|
|
24
31
|
attribute :benchmark_result, BenchmarkResult
|
|
25
32
|
|
|
33
|
+
key_value do
|
|
34
|
+
map 'platform', to: :platform
|
|
35
|
+
map 'metadata', to: :metadata
|
|
36
|
+
map 'environment_config', to: :environment_config
|
|
37
|
+
map 'benchmark_config', to: :benchmark_config
|
|
38
|
+
map 'benchmark_result', to: :benchmark_result
|
|
39
|
+
end
|
|
40
|
+
|
|
26
41
|
def self.load(path)
|
|
27
42
|
raise ArgumentError, "Path does not exist: #{path}" unless Dir.exist?(path)
|
|
28
43
|
|
|
@@ -31,7 +46,19 @@ module Serialbench
|
|
|
31
46
|
|
|
32
47
|
raise ArgumentError, "No results data found in #{path}" unless File.exist?(data_file)
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
yaml_content = IO.read(data_file)
|
|
50
|
+
|
|
51
|
+
# Debug: Check if yaml_content is empty or too small
|
|
52
|
+
if yaml_content.nil? || yaml_content.strip.empty?
|
|
53
|
+
raise ArgumentError, "Results file at #{data_file} is empty"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if yaml_content.bytesize < 200
|
|
57
|
+
warn "WARNING: Results file at #{data_file} is suspiciously small (#{yaml_content.bytesize} bytes)"
|
|
58
|
+
warn "Content preview: #{yaml_content[0..100]}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
from_yaml(yaml_content)
|
|
35
62
|
end
|
|
36
63
|
|
|
37
64
|
def self.find_all(base_path = 'results/runs')
|
|
@@ -47,12 +47,20 @@ module Serialbench
|
|
|
47
47
|
|
|
48
48
|
result = Result.load(result_path)
|
|
49
49
|
|
|
50
|
+
# Validate that the result has required fields
|
|
51
|
+
raise ArgumentError, "Result from #{result_path} is missing platform information" if result.platform.nil?
|
|
52
|
+
raise ArgumentError, "Result from #{result_path} is missing environment_config" if result.environment_config.nil?
|
|
53
|
+
raise ArgumentError, "Result from #{result_path} is missing benchmark_config" if result.benchmark_config.nil?
|
|
54
|
+
|
|
50
55
|
# Check if result already exists:
|
|
51
56
|
# If environment_config.created_at is identical;
|
|
52
57
|
# If platform.platform_string is identical;
|
|
53
58
|
# If benchmark_config.benchmark_name is identical;
|
|
54
59
|
|
|
55
60
|
duplicates = results.select do |r|
|
|
61
|
+
# Skip results with nil platform (defensive check)
|
|
62
|
+
next if r.platform.nil? || result.platform.nil?
|
|
63
|
+
|
|
56
64
|
r.platform.platform_string == result.platform.platform_string &&
|
|
57
65
|
r.environment_config.created_at == result.environment_config.created_at &&
|
|
58
66
|
r.benchmark_config.benchmark_name == result.benchmark_config.benchmark_name
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
4
|
-
require 'json'
|
|
3
|
+
require 'octokit'
|
|
5
4
|
require 'yaml'
|
|
6
5
|
require 'fileutils'
|
|
7
6
|
|
|
@@ -34,9 +33,7 @@ module Serialbench
|
|
|
34
33
|
def show_definition(tag)
|
|
35
34
|
definitions = load_definitions_from_cache
|
|
36
35
|
|
|
37
|
-
unless definitions.include?(tag)
|
|
38
|
-
raise "Ruby-Build definition '#{tag}' not found. Available definitions: #{definitions.length}"
|
|
39
|
-
end
|
|
36
|
+
raise "Ruby-Build definition '#{tag}' not found. Available definitions: #{definitions.length}" unless definitions.include?(tag)
|
|
40
37
|
|
|
41
38
|
{
|
|
42
39
|
tag: tag,
|
|
@@ -66,7 +63,7 @@ module Serialbench
|
|
|
66
63
|
variations = [
|
|
67
64
|
ruby_version,
|
|
68
65
|
"#{ruby_version}.0",
|
|
69
|
-
ruby_version.split('.')[0..1].join('.')
|
|
66
|
+
"#{ruby_version.split('.')[0..1].join('.')}.0"
|
|
70
67
|
]
|
|
71
68
|
|
|
72
69
|
variations.each do |variation|
|
|
@@ -101,30 +98,28 @@ module Serialbench
|
|
|
101
98
|
private
|
|
102
99
|
|
|
103
100
|
def fetch_definitions_from_github
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
request = Net::HTTP::Get.new(uri)
|
|
110
|
-
request['Accept'] = 'application/vnd.github.v3+json'
|
|
111
|
-
request['User-Agent'] = 'Serialbench Ruby-Build Manager'
|
|
112
|
-
|
|
113
|
-
response = http.request(request)
|
|
101
|
+
# Initialize client with optional authentication
|
|
102
|
+
# Falls back to unauthenticated if GITHUB_TOKEN not set
|
|
103
|
+
client_options = {}
|
|
104
|
+
client_options[:access_token] = ENV['GITHUB_TOKEN'] if ENV['GITHUB_TOKEN']
|
|
114
105
|
|
|
115
|
-
|
|
106
|
+
client = Octokit::Client.new(client_options)
|
|
116
107
|
|
|
117
|
-
|
|
108
|
+
contents = client.contents('rbenv/ruby-build', path: 'share/ruby-build')
|
|
118
109
|
|
|
119
110
|
# Extract definition names from the file list
|
|
120
|
-
definitions =
|
|
121
|
-
.select { |item| item[
|
|
122
|
-
.map { |item| item[
|
|
111
|
+
definitions = contents
|
|
112
|
+
.select { |item| item[:type] == 'file' && item[:name] != 'Makefile' }
|
|
113
|
+
.map { |item| item[:name] }
|
|
123
114
|
.sort
|
|
124
115
|
|
|
125
116
|
raise 'No Ruby-Build definitions found in GitHub response' if definitions.empty?
|
|
126
117
|
|
|
127
118
|
definitions
|
|
119
|
+
rescue Octokit::Error => e
|
|
120
|
+
warn "Warning: Error fetching Ruby-Build definitions from GitHub: #{e.message}"
|
|
121
|
+
warn "Note: Set GITHUB_TOKEN environment variable to avoid rate limits"
|
|
122
|
+
[]
|
|
128
123
|
end
|
|
129
124
|
|
|
130
125
|
def save_definitions_to_cache(definitions)
|
|
@@ -141,12 +136,13 @@ module Serialbench
|
|
|
141
136
|
end
|
|
142
137
|
|
|
143
138
|
def load_definitions_from_cache
|
|
144
|
-
|
|
139
|
+
return [] unless cache_exists?
|
|
145
140
|
|
|
146
141
|
cache_data = YAML.load_file(CACHE_FILE)
|
|
147
142
|
cache_data['definitions'] || []
|
|
148
143
|
rescue StandardError => e
|
|
149
|
-
|
|
144
|
+
warn "Warning: Failed to load Ruby-Build definitions from cache: #{e.message}"
|
|
145
|
+
[]
|
|
150
146
|
end
|
|
151
147
|
end
|
|
152
148
|
end
|
|
@@ -134,7 +134,7 @@ module Serialbench
|
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
# Run benchmark for specific Ruby version
|
|
137
|
-
def run_benchmark(benchmark_config,
|
|
137
|
+
def run_benchmark(benchmark_config, _benchmark_config_path, result_dir)
|
|
138
138
|
puts "🚀 Running benchmark for #{@name}..."
|
|
139
139
|
|
|
140
140
|
FileUtils.mkdir_p(result_dir)
|
|
@@ -78,9 +78,7 @@ module Serialbench
|
|
|
78
78
|
|
|
79
79
|
# Validate Docker is available
|
|
80
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
|
|
81
|
+
raise DockerError, 'Docker is not installed or not available in PATH' unless system('docker --version > /dev/null 2>&1')
|
|
84
82
|
|
|
85
83
|
return if system('docker info > /dev/null 2>&1')
|
|
86
84
|
|
|
@@ -94,7 +92,7 @@ module Serialbench
|
|
|
94
92
|
end
|
|
95
93
|
|
|
96
94
|
# Run benchmark in container
|
|
97
|
-
def run_benchmark_in_container(
|
|
95
|
+
def run_benchmark_in_container(_benchmark_config, benchmark_config_path, result_dir)
|
|
98
96
|
puts '🏃 Running benchmark in Docker container...'
|
|
99
97
|
puts " 📁 Results will be saved to: #{result_dir}"
|
|
100
98
|
puts " 🐳 Using Docker image: #{docker_image_string}"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require_relative 'base'
|
|
5
|
+
require_relative '../benchmark_runner'
|
|
6
|
+
require_relative '../models/result'
|
|
7
|
+
require_relative '../models/platform'
|
|
8
|
+
|
|
9
|
+
module Serialbench
|
|
10
|
+
module Runners
|
|
11
|
+
# Handles local benchmark execution using the current Ruby environment
|
|
12
|
+
class LocalRunner < Base
|
|
13
|
+
def prepare
|
|
14
|
+
# For local environments, no preparation is needed
|
|
15
|
+
# The current Ruby environment is already set up
|
|
16
|
+
puts "✅ Local environment ready (using current Ruby: #{RUBY_VERSION})"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run_benchmark(benchmark_config, benchmark_config_path, result_dir)
|
|
20
|
+
puts "🏠 Running benchmark locally with Ruby #{RUBY_VERSION}..."
|
|
21
|
+
puts " 📁 Results will be saved to: #{result_dir}"
|
|
22
|
+
|
|
23
|
+
FileUtils.mkdir_p(result_dir)
|
|
24
|
+
|
|
25
|
+
# Run the benchmark
|
|
26
|
+
runner = Serialbench::BenchmarkRunner.new(
|
|
27
|
+
environment_config: @environment_config,
|
|
28
|
+
benchmark_config: benchmark_config
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
results = runner.run_all_benchmarks
|
|
32
|
+
|
|
33
|
+
# Get platform information with correct Ruby version from environment config
|
|
34
|
+
platform = Serialbench::Models::Platform.current_local(
|
|
35
|
+
ruby_version: @environment_config.ruby_build_tag
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Create metadata
|
|
39
|
+
metadata = Models::RunMetadata.new(
|
|
40
|
+
benchmark_config_path: benchmark_config_path,
|
|
41
|
+
environment_config_path: @environment_config_path,
|
|
42
|
+
tags: [
|
|
43
|
+
'local',
|
|
44
|
+
platform.os,
|
|
45
|
+
platform.arch,
|
|
46
|
+
"ruby-#{@environment_config.ruby_build_tag}"
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Save results
|
|
51
|
+
results_model = Models::Result.new(
|
|
52
|
+
platform: platform,
|
|
53
|
+
metadata: metadata,
|
|
54
|
+
environment_config: @environment_config,
|
|
55
|
+
benchmark_config: benchmark_config,
|
|
56
|
+
benchmark_result: results
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Restore YAML to use Psych for output, otherwise lutaml-model's to_yaml
|
|
60
|
+
# will have no output (Syck gem overrides YAML constant)
|
|
61
|
+
Object.const_set(:YAML, Psych)
|
|
62
|
+
|
|
63
|
+
results_file = File.join(result_dir, 'results.yaml')
|
|
64
|
+
results_model.to_file(results_file)
|
|
65
|
+
|
|
66
|
+
puts "✅ Benchmark completed successfully"
|
|
67
|
+
puts " 📊 Results saved to: #{results_file}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -25,7 +25,7 @@ module Serialbench
|
|
|
25
25
|
raise NotImplementedError, 'Subclasses must implement #generate'
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def stream_parse(data
|
|
28
|
+
def stream_parse(data)
|
|
29
29
|
# Default implementation falls back to regular parsing
|
|
30
30
|
# Override in subclasses that support streaming
|
|
31
31
|
result = parse(data)
|