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.
- checksums.yaml +4 -4
- data/.github/workflows/benchmark.yml +181 -30
- data/.github/workflows/ci.yml +3 -3
- data/.github/workflows/docker.yml +272 -0
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +25 -0
- data/Gemfile +6 -30
- data/README.adoc +381 -415
- 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.ubuntu +32 -0
- data/docker/README.md +214 -0
- data/exe/serialbench +1 -1
- data/lib/serialbench/benchmark_runner.rb +270 -350
- 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 +59 -410
- 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/rapidjson_serializer.rb +50 -0
- data/lib/serialbench/serializers/json/yajl_serializer.rb +6 -4
- 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 +21 -5
- 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 +32 -4
- data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +59 -0
- data/lib/serialbench/serializers/yaml/psych_serializer.rb +54 -0
- data/lib/serialbench/serializers/yaml/syck_serializer.rb +102 -0
- data/lib/serialbench/serializers.rb +34 -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 +28 -17
- metadata +192 -55
- 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
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'lutaml/model'
|
2
|
+
|
3
|
+
module Serialbench
|
4
|
+
module Models
|
5
|
+
# Configuration for comprehensive benchmarks - Full testing with all data sizes
|
6
|
+
# Used by Docker script for complete performance analysis
|
7
|
+
|
8
|
+
# data_sizes:
|
9
|
+
# - small
|
10
|
+
# - medium
|
11
|
+
# - large
|
12
|
+
|
13
|
+
# formats:
|
14
|
+
# - xml
|
15
|
+
# - json
|
16
|
+
# - yaml
|
17
|
+
# - toml
|
18
|
+
|
19
|
+
# iterations:
|
20
|
+
# small: 20
|
21
|
+
# medium: 5
|
22
|
+
# large: 2
|
23
|
+
|
24
|
+
# # Enable memory profiling for comprehensive analysis
|
25
|
+
# memory_profiling: true
|
26
|
+
|
27
|
+
# # Standard warmup iterations
|
28
|
+
# warmup_iterations: 3
|
29
|
+
|
30
|
+
# # Enable streaming benchmarks where supported
|
31
|
+
# streaming_benchmarks: true
|
32
|
+
|
33
|
+
class BenchmarkIteration < Lutaml::Model::Serializable
|
34
|
+
attribute :small, :integer, default: -> { 20 }
|
35
|
+
attribute :medium, :integer, default: -> { 5 }
|
36
|
+
attribute :large, :integer, default: -> { 2 }
|
37
|
+
|
38
|
+
key_value do
|
39
|
+
map 'small', to: :small
|
40
|
+
map 'medium', to: :medium
|
41
|
+
map 'large', to: :large
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class BenchmarkConfig < Lutaml::Model::Serializable
|
46
|
+
attribute :name, :string
|
47
|
+
attribute :description, :string
|
48
|
+
attribute :data_sizes, :string, collection: true, values: %w[small medium large]
|
49
|
+
attribute :formats, :string, collection: true, values: %w[xml json yaml toml]
|
50
|
+
attribute :iterations, BenchmarkIteration
|
51
|
+
attribute :warmup, :integer, default: -> { 1 }
|
52
|
+
attribute :operations, :string, collection: true, values: %w[parse generate memory streaming]
|
53
|
+
|
54
|
+
def to_file(file_path)
|
55
|
+
File.write(file_path, to_yaml)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.from_file(file_path)
|
59
|
+
from_yaml(IO.read(file_path))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'lutaml/model'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Models
|
7
|
+
class SerializerInformation < Lutaml::Model::Serializable
|
8
|
+
attribute :format, :string, values: %w[xml json yaml toml]
|
9
|
+
attribute :name, :string
|
10
|
+
attribute :version, :string
|
11
|
+
end
|
12
|
+
|
13
|
+
class SerializerInformationCollection < Lutaml::Model::Collection
|
14
|
+
instances :items, SerializerInformation
|
15
|
+
end
|
16
|
+
|
17
|
+
class AdapterPerformance < Lutaml::Model::Serializable
|
18
|
+
attribute :adapter, :string
|
19
|
+
attribute :format, :string, values: %w[xml json yaml toml]
|
20
|
+
attribute :data_size, :string, values: %w[small medium large]
|
21
|
+
end
|
22
|
+
|
23
|
+
class IterationPerformance < AdapterPerformance
|
24
|
+
attribute :time_per_iterations, :float
|
25
|
+
attribute :time_per_iteration, :float
|
26
|
+
attribute :iterations_per_second, :float
|
27
|
+
attribute :iterations_count, :integer
|
28
|
+
end
|
29
|
+
|
30
|
+
class MemoryPerformance < AdapterPerformance
|
31
|
+
attribute :total_allocated, :integer
|
32
|
+
attribute :total_retained, :integer
|
33
|
+
attribute :allocated_memory, :integer
|
34
|
+
attribute :retained_memory, :integer
|
35
|
+
end
|
36
|
+
|
37
|
+
class BenchmarkResult < Lutaml::Model::Serializable
|
38
|
+
attribute :serializers, SerializerInformation, collection: true
|
39
|
+
attribute :parsing, IterationPerformance, collection: true
|
40
|
+
attribute :generation, IterationPerformance, collection: true
|
41
|
+
attribute :memory, MemoryPerformance, collection: true
|
42
|
+
attribute :streaming, IterationPerformance, collection: true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'lutaml/model'
|
2
|
+
require_relative '../ruby_build_manager'
|
3
|
+
|
4
|
+
module Serialbench
|
5
|
+
module Models
|
6
|
+
# ---
|
7
|
+
# name: docker-ruby-3.2
|
8
|
+
# kind: docker
|
9
|
+
# created_at: '2025-06-13T15:18:43+08:00'
|
10
|
+
# ruby_build_tag: "3.2.4"
|
11
|
+
# description: Docker environment for Ruby 3.2 benchmarks
|
12
|
+
# docker:
|
13
|
+
# image: 'ruby:3.2-slim'
|
14
|
+
# dockerfile: '../../docker/Dockerfile.ubuntu'
|
15
|
+
|
16
|
+
class DockerEnvConfig < Lutaml::Model::Serializable
|
17
|
+
attribute :image, :string
|
18
|
+
attribute :dockerfile, :string
|
19
|
+
|
20
|
+
key_value do
|
21
|
+
map 'image', to: :image
|
22
|
+
map 'dockerfile', to: :dockerfile
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# ---
|
27
|
+
# name: ruby-324-asdf
|
28
|
+
# kind: asdf
|
29
|
+
# created_at: '2025-06-12T22:54:43+08:00'
|
30
|
+
# ruby_build_tag: 3.2.4
|
31
|
+
# description: ASDF environment
|
32
|
+
# asdf:
|
33
|
+
# auto_install: true
|
34
|
+
class AsdfEnvConfig < Lutaml::Model::Serializable
|
35
|
+
attribute :auto_install, :boolean, default: -> { true }
|
36
|
+
|
37
|
+
key_value do
|
38
|
+
map 'auto_install', to: :auto_install
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class EnvironmentConfig < Lutaml::Model::Serializable
|
43
|
+
attribute :name, :string
|
44
|
+
attribute :kind, :string
|
45
|
+
attribute :created_at, :string, default: -> { Time.now.utc.iso8601 }
|
46
|
+
attribute :ruby_build_tag, :string, values:
|
47
|
+
RubyBuildManager.list_definitions
|
48
|
+
attribute :description, :string
|
49
|
+
attribute :docker, DockerEnvConfig
|
50
|
+
attribute :asdf, AsdfEnvConfig
|
51
|
+
|
52
|
+
key_value do
|
53
|
+
map 'name', to: :name
|
54
|
+
map 'description', to: :description
|
55
|
+
map 'kind', to: :kind
|
56
|
+
map 'created_at', to: :created_at
|
57
|
+
map 'ruby_build_tag', to: :ruby_build_tag
|
58
|
+
map 'docker', to: :docker
|
59
|
+
map 'asdf', to: :asdf
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_file(file_path)
|
63
|
+
File.write(file_path, to_yaml)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.from_file(file_path)
|
67
|
+
from_yaml(IO.read(file_path))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'lutaml/model'
|
4
|
+
require_relative '../ruby_build_manager'
|
5
|
+
|
6
|
+
module Serialbench
|
7
|
+
module Models
|
8
|
+
# platform:
|
9
|
+
# platform_string: docker-ruby-3.0
|
10
|
+
# kind: docker
|
11
|
+
# os: linux
|
12
|
+
# arch: arm64
|
13
|
+
# ruby_build_tag: 3.0.7
|
14
|
+
|
15
|
+
class Platform < Lutaml::Model::Serializable
|
16
|
+
attribute :platform_string, :string
|
17
|
+
attribute :kind, :string, default: -> { 'local' }
|
18
|
+
attribute :os, :string, default: -> { detect_os }
|
19
|
+
attribute :arch, :string, default: -> { detect_arch }
|
20
|
+
attribute :ruby_version, :string, default: -> { RUBY_VERSION }
|
21
|
+
attribute :ruby_platform, :string, default: -> { RUBY_PLATFORM }
|
22
|
+
attribute :ruby_build_tag, :string, values: RubyBuildManager.list_definitions
|
23
|
+
|
24
|
+
def self.current_local
|
25
|
+
new(
|
26
|
+
platform_string: "local-#{RUBY_VERSION}",
|
27
|
+
kind: 'local',
|
28
|
+
ruby_build_tag: RUBY_VERSION
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.detect_os
|
33
|
+
case RbConfig::CONFIG['host_os']
|
34
|
+
when /darwin/i
|
35
|
+
'macos'
|
36
|
+
when /linux/i
|
37
|
+
'linux'
|
38
|
+
when /mswin|mingw|cygwin/i
|
39
|
+
'windows'
|
40
|
+
else
|
41
|
+
'unknown'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.detect_arch
|
46
|
+
case RbConfig::CONFIG['host_cpu']
|
47
|
+
when /x86_64|amd64/i
|
48
|
+
'x86_64'
|
49
|
+
when /aarch64|arm64/i
|
50
|
+
'arm64'
|
51
|
+
when /arm/i
|
52
|
+
'arm'
|
53
|
+
else
|
54
|
+
'unknown'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
require 'fileutils'
|
5
|
+
require_relative 'platform'
|
6
|
+
require_relative 'benchmark_result'
|
7
|
+
require_relative 'benchmark_config'
|
8
|
+
require_relative 'environment_config'
|
9
|
+
|
10
|
+
module Serialbench
|
11
|
+
module Models
|
12
|
+
class RunMetadata < Lutaml::Model::Serializable
|
13
|
+
attribute :created_at, :string, default: -> { Time.now.utc.iso8601 }
|
14
|
+
attribute :benchmark_config_path, :string
|
15
|
+
attribute :environment_config_path, :string
|
16
|
+
attribute :tags, :string, collection: true
|
17
|
+
end
|
18
|
+
|
19
|
+
class Result < Lutaml::Model::Serializable
|
20
|
+
attribute :platform, Platform
|
21
|
+
attribute :metadata, RunMetadata
|
22
|
+
attribute :environment_config, EnvironmentConfig
|
23
|
+
attribute :benchmark_config, BenchmarkConfig
|
24
|
+
attribute :benchmark_result, BenchmarkResult
|
25
|
+
|
26
|
+
def self.load(path)
|
27
|
+
raise ArgumentError, "Path does not exist: #{path}" unless Dir.exist?(path)
|
28
|
+
|
29
|
+
# Load benchmark data
|
30
|
+
data_file = File.join(path, 'results.yaml')
|
31
|
+
|
32
|
+
raise ArgumentError, "No results data found in #{path}" unless File.exist?(data_file)
|
33
|
+
|
34
|
+
from_yaml(IO.read(data_file))
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.find_all(base_path = 'results/runs')
|
38
|
+
return [] unless Dir.exist?(base_path)
|
39
|
+
|
40
|
+
Dir.glob(File.join(base_path, '*')).select { |path| Dir.exist?(path) }.map do |path|
|
41
|
+
load(path)
|
42
|
+
rescue StandardError => e
|
43
|
+
warn "Failed to load run result from #{path}: #{e.message}"
|
44
|
+
nil
|
45
|
+
end.compact
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_file(path)
|
49
|
+
File.write(path, to_yaml)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'time'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module Serialbench
|
8
|
+
module Models
|
9
|
+
# ResultSet model for managing collections of benchmark results
|
10
|
+
class ResultSet < Lutaml::Model::Serializable
|
11
|
+
attribute :name, :string
|
12
|
+
attribute :description, :string
|
13
|
+
attribute :created_at, :string, default: -> { Time.now.utc.iso8601 }
|
14
|
+
attribute :updated_at, :string, default: -> { Time.now.utc.iso8601 }
|
15
|
+
attribute :results, Result, collection: true, initialize_empty: true
|
16
|
+
|
17
|
+
def self.load(path)
|
18
|
+
raise ArgumentError, "Path does not exist: #{path}" unless Dir.exist?(path)
|
19
|
+
|
20
|
+
# Load benchmark data
|
21
|
+
data_file = File.join(path, 'resultset.yaml')
|
22
|
+
|
23
|
+
raise ArgumentError, "No results data found in #{path}" unless File.exist?(data_file)
|
24
|
+
|
25
|
+
from_yaml(IO.read(data_file))
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_file(path)
|
29
|
+
File.write(path, to_yaml)
|
30
|
+
end
|
31
|
+
|
32
|
+
def save(dir)
|
33
|
+
FileUtils.mkdir_p(dir)
|
34
|
+
to_file(File.join(dir, 'resultset.yaml'))
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_result(result_path)
|
38
|
+
# Assume result_path is the directory containing benchmark results and is named
|
39
|
+
# accordingly
|
40
|
+
result_name = File.basename(result_path)
|
41
|
+
raise ArgumentError, 'Result name cannot be empty' if result_name.empty?
|
42
|
+
# Validate that the result path is a directory
|
43
|
+
raise ArgumentError, 'Result path must be a directory' unless File.directory?(result_path)
|
44
|
+
|
45
|
+
result_file_path = File.join(result_path, 'results.yaml')
|
46
|
+
raise ArgumentError, "No results data found in #{result_path}" unless File.exist?(result_file_path)
|
47
|
+
|
48
|
+
result = Result.load(result_path)
|
49
|
+
|
50
|
+
# Check if result already exists:
|
51
|
+
# If environment_config.created_at is identical;
|
52
|
+
# If platform.platform_string is identical;
|
53
|
+
# If benchmark_config.benchmark_name is identical;
|
54
|
+
|
55
|
+
duplicates = results.select do |r|
|
56
|
+
r.platform.platform_string == result.platform.platform_string &&
|
57
|
+
r.environment_config.created_at == result.environment_config.created_at &&
|
58
|
+
r.benchmark_config.benchmark_name == result.benchmark_config.benchmark_name
|
59
|
+
end
|
60
|
+
|
61
|
+
raise ArgumentError, 'Result is already present in this resultset' if duplicates.any?
|
62
|
+
|
63
|
+
# Add the result to the resultset
|
64
|
+
results << result
|
65
|
+
self.updated_at = Time.now.utc.iso8601
|
66
|
+
|
67
|
+
result
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require_relative 'result'
|
5
|
+
|
6
|
+
module Serialbench
|
7
|
+
module Models
|
8
|
+
class ResultStore
|
9
|
+
DEFAULT_BASE_PATH = 'results'
|
10
|
+
RUNS_PATH = 'runs'
|
11
|
+
SETS_PATH = 'sets'
|
12
|
+
|
13
|
+
attr_reader :base_path
|
14
|
+
|
15
|
+
def initialize(base_path = DEFAULT_BASE_PATH)
|
16
|
+
@base_path = base_path
|
17
|
+
ensure_results_directory
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.default
|
21
|
+
@default ||= new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Run management
|
25
|
+
def runs_path
|
26
|
+
File.join(@base_path, RUNS_PATH)
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_runs(tags: nil, limit: nil)
|
30
|
+
runs = Result.find_all(runs_path)
|
31
|
+
|
32
|
+
runs = runs.select { |run| (Array(tags) - run.tags).empty? } if tags
|
33
|
+
|
34
|
+
limit ? runs.first(limit) : runs
|
35
|
+
end
|
36
|
+
|
37
|
+
# Run set management
|
38
|
+
def sets_path
|
39
|
+
File.join(@base_path, SETS_PATH)
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_resultsets(tags: nil, limit: nil)
|
43
|
+
resultsets = ResultSet.find_all(sets_path)
|
44
|
+
|
45
|
+
resultsets = resultsets.select { |resultset| (Array(tags) - resultset.tags).empty? } if tags
|
46
|
+
|
47
|
+
limit ? resultsets.first(limit) : resultsets
|
48
|
+
end
|
49
|
+
|
50
|
+
# Convenience methods
|
51
|
+
def create_resultset(name, run_platform_strings, metadata: {})
|
52
|
+
run_paths = run_platform_strings.map { |ps| File.join(runs_path, ps) }
|
53
|
+
resultset = ResultSet.create(name, run_paths, metadata: metadata)
|
54
|
+
save_resultset(resultset)
|
55
|
+
resultset
|
56
|
+
end
|
57
|
+
|
58
|
+
# Validation
|
59
|
+
def validate_structure
|
60
|
+
errors = []
|
61
|
+
|
62
|
+
# Check base structure
|
63
|
+
errors << "Base path does not exist: #{@base_path}" unless Dir.exist?(@base_path)
|
64
|
+
errors << "Runs directory does not exist: #{runs_path}" unless Dir.exist?(runs_path)
|
65
|
+
errors << "Sets directory does not exist: #{sets_path}" unless Dir.exist?(sets_path)
|
66
|
+
|
67
|
+
# Validate individual runs
|
68
|
+
if Dir.exist?(runs_path)
|
69
|
+
Dir.glob(File.join(runs_path, '*')).each do |run_path|
|
70
|
+
next unless Dir.exist?(run_path)
|
71
|
+
|
72
|
+
begin
|
73
|
+
run = Result.load(run_path)
|
74
|
+
run.validate!
|
75
|
+
rescue StandardError => e
|
76
|
+
errors << "Invalid result at #{run_path}: #{e.message}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Validate result sets
|
82
|
+
if Dir.exist?(sets_path)
|
83
|
+
Dir.glob(File.join(sets_path, '*')).each do |set_path|
|
84
|
+
next unless Dir.exist?(set_path)
|
85
|
+
|
86
|
+
begin
|
87
|
+
resultset = ResultSet.load(set_path)
|
88
|
+
resultset.validate!
|
89
|
+
rescue StandardError => e
|
90
|
+
errors << "Invalid result set at #{set_path}: #{e.message}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
errors
|
96
|
+
end
|
97
|
+
|
98
|
+
def valid?
|
99
|
+
validate_structure.empty?
|
100
|
+
end
|
101
|
+
|
102
|
+
def ensure_results_directory
|
103
|
+
FileUtils.mkdir_p(runs_path) unless Dir.exist?(runs_path)
|
104
|
+
FileUtils.mkdir_p(sets_path) unless Dir.exist?(sets_path)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'models/benchmark_result'
|
4
|
+
require_relative 'models/platform'
|
5
|
+
require_relative 'models/result'
|
6
|
+
require_relative 'models/result_store'
|
7
|
+
require_relative 'models/benchmark_config'
|
8
|
+
require_relative 'models/environment_config'
|
9
|
+
require_relative 'models/result_set'
|
10
|
+
|
11
|
+
module Serialbench
|
12
|
+
module Models
|
13
|
+
# Factory method to create appropriate model based on data structure
|
14
|
+
def self.from_file(file_path)
|
15
|
+
case File.extname(file_path).downcase
|
16
|
+
when '.yaml', '.yml'
|
17
|
+
data = YAML.load_file(file_path)
|
18
|
+
when '.json'
|
19
|
+
data = JSON.parse(File.read(file_path))
|
20
|
+
else
|
21
|
+
raise ArgumentError, "Unsupported file format: #{File.extname(file_path)}"
|
22
|
+
end
|
23
|
+
|
24
|
+
from_data(data)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.from_data(data)
|
28
|
+
BenchmarkResult.new(data)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Convert any benchmark result to YAML format
|
32
|
+
def self.to_yaml_file(result, file_path)
|
33
|
+
result.to_yaml_file(file_path)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Convert any benchmark result to JSON format (for HTML templates)
|
37
|
+
def self.to_json_file(result, file_path)
|
38
|
+
result.to_json_file(file_path)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Convenience methods for the new OO architecture
|
42
|
+
def self.result_store
|
43
|
+
ResultStore.default
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.create_run(platform_string, benchmark_data, metadata: {})
|
47
|
+
Result.create(platform_string, benchmark_data, metadata: metadata)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.create_resultset(name, run_paths_or_objects, metadata: {})
|
51
|
+
ResultSet.create(name, run_paths_or_objects, metadata: metadata)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
require 'yaml'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
module Serialbench
|
9
|
+
# Manages Ruby-Build definitions from the official ruby-build repository
|
10
|
+
class RubyBuildManager
|
11
|
+
GITHUB_API_URL = 'https://api.github.com/repos/rbenv/ruby-build/contents/share/ruby-build'
|
12
|
+
CACHE_DIR = File.expand_path('~/.serialbench')
|
13
|
+
CACHE_FILE = File.join(CACHE_DIR, 'ruby-build-definitions.yaml')
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def update_definitions
|
17
|
+
puts '🔄 Fetching Ruby-Build definitions from GitHub...'
|
18
|
+
|
19
|
+
definitions = fetch_definitions_from_github
|
20
|
+
save_definitions_to_cache(definitions)
|
21
|
+
|
22
|
+
puts "✅ Updated #{definitions.length} Ruby-Build definitions"
|
23
|
+
puts "📁 Cache location: #{CACHE_FILE}"
|
24
|
+
|
25
|
+
definitions
|
26
|
+
rescue StandardError => e
|
27
|
+
raise "Failed to update Ruby-Build definitions: #{e.message}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def list_definitions
|
31
|
+
load_definitions_from_cache
|
32
|
+
end
|
33
|
+
|
34
|
+
def show_definition(tag)
|
35
|
+
definitions = load_definitions_from_cache
|
36
|
+
|
37
|
+
unless definitions.include?(tag)
|
38
|
+
raise "Ruby-Build definition '#{tag}' not found. Available definitions: #{definitions.length}"
|
39
|
+
end
|
40
|
+
|
41
|
+
{
|
42
|
+
tag: tag,
|
43
|
+
available: true,
|
44
|
+
source: 'ruby-build',
|
45
|
+
cache_file: CACHE_FILE
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_tag(tag)
|
50
|
+
puts "🔍 Validating Ruby-Build tag: #{tag} against #{CACHE_FILE}"
|
51
|
+
return false if tag.nil? || tag.strip.empty?
|
52
|
+
|
53
|
+
definitions = load_definitions_from_cache
|
54
|
+
definitions.include?(tag)
|
55
|
+
rescue StandardError
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def suggest_current_ruby_tag
|
60
|
+
ruby_version = RUBY_VERSION
|
61
|
+
|
62
|
+
# Try exact match first
|
63
|
+
return ruby_version if validate_tag(ruby_version)
|
64
|
+
|
65
|
+
# Try common variations
|
66
|
+
variations = [
|
67
|
+
ruby_version,
|
68
|
+
"#{ruby_version}.0",
|
69
|
+
ruby_version.split('.')[0..1].join('.') + '.0'
|
70
|
+
]
|
71
|
+
|
72
|
+
variations.each do |variation|
|
73
|
+
return variation if validate_tag(variation)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return the Ruby version even if not found, user can adjust
|
77
|
+
ruby_version
|
78
|
+
end
|
79
|
+
|
80
|
+
def cache_exists?
|
81
|
+
File.exist?(CACHE_FILE)
|
82
|
+
end
|
83
|
+
|
84
|
+
def cache_age
|
85
|
+
return nil unless cache_exists?
|
86
|
+
|
87
|
+
Time.now - File.mtime(CACHE_FILE)
|
88
|
+
end
|
89
|
+
|
90
|
+
def ensure_cache_exists!
|
91
|
+
return if cache_exists?
|
92
|
+
|
93
|
+
raise <<~ERROR
|
94
|
+
Ruby-Build definitions cache not found.
|
95
|
+
|
96
|
+
Update the cache first:
|
97
|
+
serialbench ruby-build update
|
98
|
+
ERROR
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
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)
|
114
|
+
|
115
|
+
raise "GitHub API request failed: #{response.code} #{response.message}" unless response.code == '200'
|
116
|
+
|
117
|
+
data = JSON.parse(response.body)
|
118
|
+
|
119
|
+
# Extract definition names from the file list
|
120
|
+
definitions = data
|
121
|
+
.select { |item| item['type'] == 'file' }
|
122
|
+
.map { |item| item['name'] }
|
123
|
+
.sort
|
124
|
+
|
125
|
+
raise 'No Ruby-Build definitions found in GitHub response' if definitions.empty?
|
126
|
+
|
127
|
+
definitions
|
128
|
+
end
|
129
|
+
|
130
|
+
def save_definitions_to_cache(definitions)
|
131
|
+
FileUtils.mkdir_p(CACHE_DIR)
|
132
|
+
|
133
|
+
cache_data = {
|
134
|
+
'updated_at' => Time.now.utc.iso8601,
|
135
|
+
'source' => GITHUB_API_URL,
|
136
|
+
'count' => definitions.length,
|
137
|
+
'definitions' => definitions
|
138
|
+
}
|
139
|
+
|
140
|
+
File.write(CACHE_FILE, cache_data.to_yaml)
|
141
|
+
end
|
142
|
+
|
143
|
+
def load_definitions_from_cache
|
144
|
+
ensure_cache_exists!
|
145
|
+
|
146
|
+
cache_data = YAML.load_file(CACHE_FILE)
|
147
|
+
cache_data['definitions'] || []
|
148
|
+
rescue StandardError => e
|
149
|
+
raise "Failed to load Ruby-Build definitions from cache: #{e.message}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|