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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +273 -228
  3. data/.github/workflows/rake.yml +11 -0
  4. data/.github/workflows/windows-debug.yml +171 -0
  5. data/.gitignore +32 -0
  6. data/.rubocop.yml +1 -0
  7. data/.rubocop_todo.yml +274 -0
  8. data/Gemfile +13 -1
  9. data/README.adoc +36 -0
  10. data/data/schemas/result.yml +29 -0
  11. data/docs/PLATFORM_VALIDATION_FIX.md +79 -0
  12. data/docs/SYCK_YAML_FIX.md +91 -0
  13. data/docs/WEBSITE_COMPLETION_PLAN.md +440 -0
  14. data/docs/WINDOWS_LIBXML_FIX.md +136 -0
  15. data/docs/WINDOWS_SETUP.md +122 -0
  16. data/lib/serialbench/benchmark_runner.rb +3 -3
  17. data/lib/serialbench/cli/benchmark_cli.rb +74 -1
  18. data/lib/serialbench/cli/environment_cli.rb +3 -3
  19. data/lib/serialbench/cli/resultset_cli.rb +72 -26
  20. data/lib/serialbench/cli/ruby_build_cli.rb +75 -88
  21. data/lib/serialbench/cli/validate_cli.rb +88 -0
  22. data/lib/serialbench/cli.rb +6 -2
  23. data/lib/serialbench/config_manager.rb +15 -26
  24. data/lib/serialbench/models/benchmark_config.rb +12 -0
  25. data/lib/serialbench/models/benchmark_result.rb +39 -3
  26. data/lib/serialbench/models/environment_config.rb +3 -2
  27. data/lib/serialbench/models/platform.rb +56 -4
  28. data/lib/serialbench/models/result.rb +28 -1
  29. data/lib/serialbench/models/result_set.rb +8 -0
  30. data/lib/serialbench/ruby_build_manager.rb +19 -23
  31. data/lib/serialbench/runners/asdf_runner.rb +1 -1
  32. data/lib/serialbench/runners/docker_runner.rb +2 -4
  33. data/lib/serialbench/runners/local_runner.rb +71 -0
  34. data/lib/serialbench/serializers/base_serializer.rb +1 -1
  35. data/lib/serialbench/serializers/json/rapidjson_serializer.rb +1 -1
  36. data/lib/serialbench/serializers/toml/base_toml_serializer.rb +0 -2
  37. data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +1 -1
  38. data/lib/serialbench/serializers/toml/tomlib_serializer.rb +1 -1
  39. data/lib/serialbench/serializers/xml/libxml_serializer.rb +4 -8
  40. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +2 -2
  41. data/lib/serialbench/serializers/xml/oga_serializer.rb +4 -8
  42. data/lib/serialbench/serializers/xml/ox_serializer.rb +2 -2
  43. data/lib/serialbench/serializers/xml/rexml_serializer.rb +3 -3
  44. data/lib/serialbench/serializers/yaml/psych_serializer.rb +1 -1
  45. data/lib/serialbench/serializers/yaml/syck_serializer.rb +1 -1
  46. data/lib/serialbench/serializers.rb +2 -2
  47. data/lib/serialbench/site_generator.rb +180 -2
  48. data/lib/serialbench/templates/assets/css/format_based.css +1 -53
  49. data/lib/serialbench/templates/assets/css/themes.css +5 -4
  50. data/lib/serialbench/templates/assets/js/chart_helpers.js +44 -14
  51. data/lib/serialbench/templates/assets/js/dashboard.js +14 -15
  52. data/lib/serialbench/templates/format_based.liquid +480 -252
  53. data/lib/serialbench/version.rb +1 -1
  54. data/lib/serialbench/yaml_validator.rb +36 -0
  55. data/serialbench.gemspec +11 -2
  56. metadata +34 -23
  57. data/.github/workflows/ci.yml +0 -74
  58. 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
@@ -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, *args)
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?(method_name, include_private = false)
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, config_path)
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
- unless invalid_variants.empty?
90
- raise ConfigurationError, "Invalid image variants: #{invalid_variants.join(', ')}. Valid variants: slim, alpine"
91
- end
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, "Invalid runtime: #{config_data['runtime']}. Valid runtimes: #{valid_runtimes.join(', ')}"
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
- if config_data['ruby_versions'].empty?
131
- raise ConfigurationError, "ruby_versions cannot be empty"
132
- end
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
- class SerializerInformationCollection < Lutaml::Model::Collection
14
- instances :items, SerializerInformation
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, values:
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, values: RubyBuildManager.list_definitions
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: "local-#{RUBY_VERSION}",
49
+ platform_string: platform_string,
27
50
  kind: 'local',
28
- ruby_build_tag: RUBY_VERSION
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
- from_yaml(IO.read(data_file))
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 'net/http'
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('.') + '.0'
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
- uri = URI(GITHUB_API_URL)
105
-
106
- http = Net::HTTP.new(uri.host, uri.port)
107
- http.use_ssl = true
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
- raise "GitHub API request failed: #{response.code} #{response.message}" unless response.code == '200'
106
+ client = Octokit::Client.new(client_options)
116
107
 
117
- data = JSON.parse(response.body)
108
+ contents = client.contents('rbenv/ruby-build', path: 'share/ruby-build')
118
109
 
119
110
  # Extract definition names from the file list
120
- definitions = data
121
- .select { |item| item['type'] == 'file' }
122
- .map { |item| item['name'] }
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
- ensure_cache_exists!
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
- raise "Failed to load Ruby-Build definitions from cache: #{e.message}"
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, benchmark_config_path, result_dir)
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(benchmark_config, benchmark_config_path, result_dir)
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, &block)
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)