serialbench 0.1.1 → 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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +273 -220
  3. data/.github/workflows/rake.yml +26 -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 +14 -1
  9. data/README.adoc +292 -1118
  10. data/Rakefile +0 -55
  11. data/config/benchmarks/full.yml +29 -0
  12. data/config/benchmarks/short.yml +26 -0
  13. data/config/environments/asdf-ruby-3.2.yml +8 -0
  14. data/config/environments/asdf-ruby-3.3.yml +8 -0
  15. data/config/environments/docker-ruby-3.0.yml +9 -0
  16. data/config/environments/docker-ruby-3.1.yml +9 -0
  17. data/config/environments/docker-ruby-3.2.yml +9 -0
  18. data/config/environments/docker-ruby-3.3.yml +9 -0
  19. data/config/environments/docker-ruby-3.4.yml +9 -0
  20. data/data/schemas/result.yml +29 -0
  21. data/docker/Dockerfile.alpine +33 -0
  22. data/docker/{Dockerfile.benchmark → Dockerfile.ubuntu} +4 -3
  23. data/docker/README.md +2 -2
  24. data/docs/PLATFORM_VALIDATION_FIX.md +79 -0
  25. data/docs/SYCK_YAML_FIX.md +91 -0
  26. data/docs/WEBSITE_COMPLETION_PLAN.md +440 -0
  27. data/docs/WINDOWS_LIBXML_FIX.md +136 -0
  28. data/docs/WINDOWS_SETUP.md +122 -0
  29. data/exe/serialbench +1 -1
  30. data/lib/serialbench/benchmark_runner.rb +261 -423
  31. data/lib/serialbench/cli/base_cli.rb +51 -0
  32. data/lib/serialbench/cli/benchmark_cli.rb +453 -0
  33. data/lib/serialbench/cli/environment_cli.rb +181 -0
  34. data/lib/serialbench/cli/resultset_cli.rb +261 -0
  35. data/lib/serialbench/cli/ruby_build_cli.rb +225 -0
  36. data/lib/serialbench/cli/validate_cli.rb +88 -0
  37. data/lib/serialbench/cli.rb +61 -600
  38. data/lib/serialbench/config_manager.rb +129 -0
  39. data/lib/serialbench/models/benchmark_config.rb +75 -0
  40. data/lib/serialbench/models/benchmark_result.rb +81 -0
  41. data/lib/serialbench/models/environment_config.rb +72 -0
  42. data/lib/serialbench/models/platform.rb +111 -0
  43. data/lib/serialbench/models/result.rb +80 -0
  44. data/lib/serialbench/models/result_set.rb +79 -0
  45. data/lib/serialbench/models/result_store.rb +108 -0
  46. data/lib/serialbench/models.rb +54 -0
  47. data/lib/serialbench/ruby_build_manager.rb +149 -0
  48. data/lib/serialbench/runners/asdf_runner.rb +296 -0
  49. data/lib/serialbench/runners/base.rb +32 -0
  50. data/lib/serialbench/runners/docker_runner.rb +140 -0
  51. data/lib/serialbench/runners/local_runner.rb +71 -0
  52. data/lib/serialbench/serializers/base_serializer.rb +9 -17
  53. data/lib/serialbench/serializers/json/base_json_serializer.rb +4 -4
  54. data/lib/serialbench/serializers/json/json_serializer.rb +0 -2
  55. data/lib/serialbench/serializers/json/oj_serializer.rb +0 -2
  56. data/lib/serialbench/serializers/json/rapidjson_serializer.rb +1 -1
  57. data/lib/serialbench/serializers/json/yajl_serializer.rb +0 -2
  58. data/lib/serialbench/serializers/toml/base_toml_serializer.rb +5 -5
  59. data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +1 -3
  60. data/lib/serialbench/serializers/toml/tomlib_serializer.rb +1 -3
  61. data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +56 -0
  62. data/lib/serialbench/serializers/xml/base_xml_serializer.rb +4 -9
  63. data/lib/serialbench/serializers/xml/libxml_serializer.rb +4 -10
  64. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +2 -4
  65. data/lib/serialbench/serializers/xml/oga_serializer.rb +4 -10
  66. data/lib/serialbench/serializers/xml/ox_serializer.rb +2 -4
  67. data/lib/serialbench/serializers/xml/rexml_serializer.rb +3 -5
  68. data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +5 -1
  69. data/lib/serialbench/serializers/yaml/psych_serializer.rb +1 -1
  70. data/lib/serialbench/serializers/yaml/syck_serializer.rb +60 -23
  71. data/lib/serialbench/serializers.rb +23 -6
  72. data/lib/serialbench/site_generator.rb +283 -0
  73. data/lib/serialbench/templates/assets/css/benchmark_report.css +535 -0
  74. data/lib/serialbench/templates/assets/css/format_based.css +474 -0
  75. data/lib/serialbench/templates/assets/css/themes.css +589 -0
  76. data/lib/serialbench/templates/assets/js/chart_helpers.js +411 -0
  77. data/lib/serialbench/templates/assets/js/dashboard.js +795 -0
  78. data/lib/serialbench/templates/assets/js/navigation.js +142 -0
  79. data/lib/serialbench/templates/base.liquid +49 -0
  80. data/lib/serialbench/templates/format_based.liquid +507 -0
  81. data/lib/serialbench/templates/partials/chart_section.liquid +4 -0
  82. data/lib/serialbench/version.rb +1 -1
  83. data/lib/serialbench/yaml_validator.rb +36 -0
  84. data/lib/serialbench.rb +2 -31
  85. data/serialbench.gemspec +15 -3
  86. metadata +106 -25
  87. data/.github/workflows/ci.yml +0 -74
  88. data/.github/workflows/docker.yml +0 -246
  89. data/config/ci.yml +0 -22
  90. data/config/full.yml +0 -30
  91. data/docker/run-benchmarks.sh +0 -356
  92. data/lib/serialbench/chart_generator.rb +0 -821
  93. data/lib/serialbench/result_formatter.rb +0 -182
  94. data/lib/serialbench/result_merger.rb +0 -1201
  95. data/lib/serialbench/serializers/xml/base_parser.rb +0 -69
  96. data/lib/serialbench/serializers/xml/libxml_parser.rb +0 -98
  97. data/lib/serialbench/serializers/xml/nokogiri_parser.rb +0 -111
  98. data/lib/serialbench/serializers/xml/oga_parser.rb +0 -85
  99. data/lib/serialbench/serializers/xml/ox_parser.rb +0 -64
  100. data/lib/serialbench/serializers/xml/rexml_parser.rb +0 -129
@@ -0,0 +1,261 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_cli'
4
+ require_relative '../models/result_set'
5
+ require_relative '../site_generator'
6
+
7
+ module Serialbench
8
+ module Cli
9
+ # CLI for managing benchmark resultsets (collections of runs)
10
+ class ResultsetCli < BaseCli
11
+ desc 'create NAME PATH', 'Create a new resultset'
12
+ long_desc <<~DESC
13
+ Create a new resultset (collection of benchmark runs).
14
+
15
+ NAME is required and must be unique.
16
+ PATH is the directory where the resultset will be created.
17
+
18
+ Examples:
19
+ serialbench resultset create performance-comparison results/sets/performance-comparison
20
+ serialbench resultset create cross-platform-test results/sets/cross-platform-test
21
+ DESC
22
+ def create(resultset_name, resultset_path)
23
+ Serialbench::Models::ResultStore.default
24
+
25
+ # Check if resultset already exists
26
+ definition_path = File.join(resultset_path, 'resultset.yaml')
27
+
28
+ if File.exist?(definition_path)
29
+ say "ResultSet at '#{resultset_path}' already exists", :yellow
30
+ return unless yes?('Create anyway with timestamp suffix? (y/n)')
31
+
32
+ resultset_path = "#{resultset_path}-#{generate_timestamp}"
33
+ end
34
+
35
+ # Create empty resultset using the new ResultSet model
36
+ resultset = Serialbench::Models::ResultSet.new(
37
+ name: resultset_name,
38
+ description: "ResultSet for #{resultset_name} benchmarks",
39
+ created_at: Time.now.utc.iso8601,
40
+ updated_at: Time.now.utc.iso8601
41
+ )
42
+ resultset.save(resultset_path)
43
+
44
+ say "✅ Created resultset: #{resultset_path}", :green
45
+ say "Path: #{definition_path}", :cyan
46
+ say "Use 'serialbench resultset add-result' to add benchmark runs", :white
47
+ rescue StandardError => e
48
+ say "Error creating resultset: #{e.message}", :red
49
+ exit 1
50
+ end
51
+
52
+ desc 'add-result RESULTSET_PATH RESULT_PATH...', 'Add one or more runs to a resultset'
53
+ long_desc <<~DESC
54
+ Add one or more benchmark runs to a resultset.
55
+
56
+ RESULTSET_PATH is the path to the resultset directory
57
+ RESULT_PATH... accepts multiple result paths (supports shell expansion)
58
+
59
+ Examples:
60
+ # Add single result
61
+ serialbench resultset add-result results/sets/weekly results/runs/my-run
62
+
63
+ # Add multiple results explicitly
64
+ serialbench resultset add-result results/sets/weekly results/runs/run1 results/runs/run2
65
+
66
+ # Add multiple results with shell expansion
67
+ serialbench resultset add-result results/sets/weekly artifacts/benchmark-results-*/
68
+ serialbench resultset add-result results/sets/weekly results/runs/*
69
+ DESC
70
+ def add_result(resultset_path, *result_paths)
71
+ if result_paths.empty?
72
+ say '❌ Error: At least one result path must be provided', :red
73
+ exit 1
74
+ end
75
+
76
+ resultset = Serialbench::Models::ResultSet.load(resultset_path)
77
+
78
+ say "📦 Adding #{result_paths.size} result(s) to resultset", :cyan
79
+ say "ResultSet: #{resultset_path}", :white
80
+ say ''
81
+
82
+ added_count = 0
83
+ failed_count = 0
84
+ skipped_count = 0
85
+
86
+ result_paths.each_with_index do |result_path, index|
87
+ say "#{index + 1}/#{result_paths.size} Processing: #{result_path}", :cyan
88
+
89
+ # Find results.yaml in the path or subdirectories
90
+ results_file = if File.exist?(File.join(result_path, 'results.yaml'))
91
+ File.join(result_path, 'results.yaml')
92
+ else
93
+ Dir.glob(File.join(result_path, '**/results.yaml')).first
94
+ end
95
+
96
+ unless results_file
97
+ say ' ⚠️ No results.yaml found - skipping', :yellow
98
+ skipped_count += 1
99
+ next
100
+ end
101
+
102
+ result_dir = File.dirname(results_file)
103
+
104
+ begin
105
+ resultset.add_result(result_dir)
106
+ say ' ✅ Added successfully', :green
107
+ added_count += 1
108
+ rescue StandardError => e
109
+ say " ❌ Failed: #{e.message}", :red
110
+ failed_count += 1
111
+ end
112
+ say ''
113
+ end
114
+
115
+ resultset.save(resultset_path)
116
+
117
+ say '=' * 60, :cyan
118
+ say 'Summary:', :green
119
+ say " Total processed: #{result_paths.size}", :white
120
+ say " ✅ Successfully added: #{added_count}", :green
121
+ say " ❌ Failed: #{failed_count}", :red if failed_count > 0
122
+ say " ⚠️ Skipped: #{skipped_count}", :yellow if skipped_count > 0
123
+ say " 📊 Total results in set: #{resultset.results.count}", :cyan
124
+ say '=' * 60, :cyan
125
+
126
+ exit 1 if failed_count > 0 && added_count == 0
127
+ rescue StandardError => e
128
+ say "❌ Error: #{e.message}", :red
129
+ exit 1
130
+ end
131
+
132
+ desc 'remove-result RESULTSET_PATH RESULT_PATH', 'Remove a run from a resultset'
133
+ long_desc <<~DESC
134
+ Remove a benchmark run from a resultset.
135
+
136
+ RESULTSET_PATH must be specified explicitly
137
+ RESULT_PATH is the path to the run result directory
138
+
139
+ Examples:
140
+ serialbench resultset remove-result results/sets/performance-comparison results/runs/my-run-local-macos-arm64-ruby-3.3.8
141
+ serialbench resultset remove-result results/sets/cross-platform-test results/runs/my-docker-run
142
+ DESC
143
+ def remove_result(resultset_path, _result_path)
144
+ Serialbench::Models::ResultStore.default
145
+
146
+ # Find the resultset
147
+ resultset = Serialbench::Models::ResultSet.load(resultset_path)
148
+ if resultset.nil?
149
+ say "ResultSet '#{resultset_path}' not found", :red
150
+ exit 1
151
+ end
152
+
153
+ # Remove run from resultset
154
+ removed = resultset.remove_run(run_identifier)
155
+ unless removed
156
+ say "Run '#{run_identifier}' not found in resultset", :yellow
157
+ say 'Available runs in resultset:', :white
158
+ resultset.runs.each do |run_info|
159
+ say " - #{run_info[:name]}", :white
160
+ end
161
+ return
162
+ end
163
+
164
+ resultset.save
165
+
166
+ say '✅ Removed run from resultset', :green
167
+ say "Run: #{run_identifier}", :cyan
168
+ say "ResultSet: #{resultset_path}", :cyan
169
+ say "Remaining runs in set: #{resultset.runs.length}", :white
170
+ rescue StandardError => e
171
+ say "Error removing run from resultset: #{e.message}", :red
172
+ exit 1
173
+ end
174
+
175
+ desc 'build-site RESULTSET_PATH [OUTPUT_DIR]', 'Generate HTML site for a resultset'
176
+ long_desc <<~DESC
177
+ Generate an HTML site for a resultset (comparative analysis).
178
+
179
+ RESULTSET_PATH must be specified explicitly
180
+ OUTPUT_DIR defaults to _site/
181
+
182
+ Examples:
183
+ serialbench resultset build-site results/sets/performance-comparison
184
+ serialbench resultset build-site results/sets/cross-platform-test output/
185
+ DESC
186
+ def build_site(resultset_path, output_dir = '_site')
187
+ unless Dir.exist?(resultset_path)
188
+ say "ResultSet directory not found: #{resultset_path}", :red
189
+ say "Please create a resultset first using 'serialbench resultset create'", :white
190
+ exit 1
191
+ end
192
+
193
+ resultset = Serialbench::Models::ResultSet.load(resultset_path)
194
+
195
+ if resultset.results.empty?
196
+ say "ResultSet '#{resultset_path}' contains no runs", :yellow
197
+ say "Use 'serialbench resultset add-result' to add runs first", :white
198
+ return
199
+ end
200
+
201
+ say "🏗️ Generating HTML site for resultset: #{resultset_path}", :green
202
+ say "Runs in set: #{resultset.results.size}", :cyan
203
+
204
+ # Use the unified site generator for resultsets
205
+ Serialbench::SiteGenerator.generate_for_resultset(resultset, output_dir)
206
+
207
+ say '✅ HTML site generated successfully!', :green
208
+ say "Site location: #{output_dir}", :cyan
209
+ say "Open: #{File.join(output_dir, 'index.html')}", :white
210
+ rescue StandardError => e
211
+ say "Error generating site: #{e.message}", :red
212
+ say "Details: #{e.backtrace.first(3).join("\n")}", :red if options[:verbose]
213
+ exit 1
214
+ end
215
+
216
+ desc 'list', 'List all available resultsets'
217
+ long_desc <<~DESC
218
+ List all resultsets in the results/sets/ directory.
219
+
220
+ Shows resultset names, number of runs, and timestamps.
221
+ DESC
222
+ def list
223
+ ensure_results_directory
224
+
225
+ begin
226
+ store = Serialbench::Models::ResultStore.default
227
+ resultsets = store.find_resultsets
228
+
229
+ if resultsets.empty?
230
+ say 'No resultsets found', :yellow
231
+ say "Use 'serialbench resultset create' to create a resultset", :white
232
+ return
233
+ end
234
+
235
+ say 'Available ResultSets:', :green
236
+ say '=' * 50, :green
237
+
238
+ resultsets.each do |resultset|
239
+ say "📁 #{resultset.name}", :cyan
240
+ say " Runs: #{resultset.runs.length}", :white
241
+ say " Created: #{resultset.metadata[:timestamp]}", :white
242
+ say " Path: #{resultset.directory}", :white
243
+
244
+ if resultset.runs.any?
245
+ say ' Contains:', :white
246
+ resultset.runs.first(3).each do |run_info|
247
+ say " - #{run_info[:name]}", :white
248
+ end
249
+ say " ... and #{resultset.runs.length - 3} more" if resultset.runs.length > 3
250
+ end
251
+
252
+ say ''
253
+ end
254
+ rescue StandardError => e
255
+ say "Error listing resultsets: #{e.message}", :red
256
+ exit 1
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_cli'
4
+ require_relative '../ruby_build_manager'
5
+
6
+ module Serialbench
7
+ module Cli
8
+ # CLI for managing Ruby-Build definitions
9
+ class RubyBuildCli < BaseCli
10
+ desc 'update', 'Update Ruby-Build definitions from GitHub'
11
+ long_desc <<~DESC
12
+ Fetch the latest Ruby-Build definitions from the official ruby-build repository
13
+ and cache them locally for validation purposes.
14
+
15
+ This command is required before using any Ruby-Build validation features.
16
+
17
+ Examples:
18
+ serialbench ruby-build update
19
+ DESC
20
+ def update
21
+ say '🔄 Updating Ruby-Build definitions...', :green
22
+
23
+ begin
24
+ definitions = RubyBuildManager.update_definitions
25
+
26
+ say "✅ Successfully updated #{definitions.length} Ruby-Build definitions", :green
27
+ say "📁 Cache location: #{RubyBuildManager::CACHE_FILE}", :cyan
28
+
29
+ # Show some examples
30
+ recent_versions = definitions.select { |d| d.match?(/^3\.[2-4]\.\d+$/) }.last(5)
31
+ if recent_versions.any?
32
+ say "\n📋 Recent Ruby versions available:", :white
33
+ recent_versions.each { |version| say " #{version}", :cyan }
34
+ end
35
+ rescue StandardError => e
36
+ say "❌ Failed to update Ruby-Build definitions: #{e.message}", :red
37
+ exit 1
38
+ end
39
+ end
40
+
41
+ desc 'list [FILTER]', 'List available Ruby-Build definitions'
42
+ long_desc <<~DESC
43
+ List all available Ruby-Build definitions from the local cache.
44
+
45
+ Optionally filter the list by providing a filter string.
46
+
47
+ Examples:
48
+ serialbench ruby-build list # List all definitions
49
+ serialbench ruby-build list 3.3 # Filter by "3.3"
50
+ serialbench ruby-build list 3.2. # Filter by "3.2."
51
+ DESC
52
+ option :limit, type: :numeric, default: 50, desc: 'Maximum number of definitions to show'
53
+ def list(filter = nil)
54
+ definitions = RubyBuildManager.list_definitions(filter: filter)
55
+
56
+ if definitions.empty?
57
+ if filter
58
+ say "No Ruby-Build definitions found matching '#{filter}'", :yellow
59
+ else
60
+ say 'No Ruby-Build definitions found in cache', :yellow
61
+ say 'Update the cache first: serialbench ruby-build update', :white
62
+ end
63
+ return
64
+ end
65
+
66
+ # Limit results if there are many
67
+ limited_definitions = definitions.first(options[:limit])
68
+
69
+ say "Ruby-Build Definitions#{filter ? " (filtered by '#{filter}')" : ''}:", :green
70
+ say '=' * 60, :green
71
+
72
+ limited_definitions.each do |definition|
73
+ say " #{definition}", :cyan
74
+ end
75
+
76
+ if definitions.length > options[:limit]
77
+ remaining = definitions.length - options[:limit]
78
+ say "\n... and #{remaining} more definitions", :yellow
79
+ say 'Use --limit to show more results', :white
80
+ end
81
+
82
+ say "\nTotal: #{definitions.length} definitions", :white
83
+ rescue StandardError => e
84
+ say "❌ Failed to list Ruby-Build definitions: #{e.message}", :red
85
+ say 'Try updating the cache: serialbench ruby-build update', :white
86
+ exit 1
87
+ end
88
+
89
+ desc 'show TAG', 'Show details for a specific Ruby-Build definition'
90
+ long_desc <<~DESC
91
+ Show detailed information about a specific Ruby-Build definition.
92
+
93
+ Examples:
94
+ serialbench ruby-build show 3.3.8
95
+ serialbench ruby-build show 3.2.4
96
+ DESC
97
+ def show(tag)
98
+ definition = RubyBuildManager.show_definition(tag)
99
+
100
+ say "Ruby-Build Definition: #{tag}", :green
101
+ say '=' * 40, :green
102
+ say "Tag: #{definition[:tag]}", :cyan
103
+ say "Available: #{definition[:available] ? '✅ Yes' : '❌ No'}", :cyan
104
+ say "Source: #{definition[:source]}", :cyan
105
+ say "Cache file: #{definition[:cache_file]}", :white
106
+ rescue StandardError => e
107
+ say "❌ #{e.message}", :red
108
+ say 'Available definitions: serialbench ruby-build list', :white
109
+ exit 1
110
+ end
111
+
112
+ desc 'validate TAG', 'Validate a Ruby-Build tag'
113
+ long_desc <<~DESC
114
+ Validate whether a Ruby-Build tag exists in the cached definitions.
115
+
116
+ Examples:
117
+ serialbench ruby-build validate 3.3.8
118
+ serialbench ruby-build validate 3.2.4
119
+ DESC
120
+ def validate(tag)
121
+ valid = RubyBuildManager.validate_tag(tag)
122
+
123
+ if valid
124
+ say "✅ Ruby-Build tag '#{tag}' is valid", :green
125
+ else
126
+ say "❌ Ruby-Build tag '#{tag}' is not valid", :red
127
+
128
+ # Suggest similar tags
129
+ definitions = RubyBuildManager.list_definitions
130
+ similar = definitions.select { |d| d.include?(tag.split('.').first(2).join('.')) }.first(5)
131
+
132
+ if similar.any?
133
+ say "\n💡 Similar available tags:", :yellow
134
+ similar.each { |s| say " #{s}", :cyan }
135
+ end
136
+
137
+ exit 1
138
+ end
139
+ rescue StandardError => e
140
+ say "❌ Failed to validate tag: #{e.message}", :red
141
+ say 'Try updating the cache: serialbench ruby-build update', :white
142
+ exit 1
143
+ end
144
+
145
+ desc 'suggest', 'Suggest Ruby-Build tag for current Ruby version'
146
+ long_desc <<~DESC
147
+ Suggest an appropriate Ruby-Build tag based on the current Ruby version.
148
+
149
+ This is useful when creating local environments to get the correct
150
+ ruby_build_tag value.
151
+
152
+ Examples:
153
+ serialbench ruby-build suggest
154
+ DESC
155
+ def suggest
156
+ current_ruby = RUBY_VERSION
157
+ suggested_tag = RubyBuildManager.suggest_current_ruby_tag
158
+
159
+ say "Current Ruby version: #{current_ruby}", :cyan
160
+ say "Suggested ruby_build_tag: #{suggested_tag}", :green
161
+
162
+ # Validate the suggestion
163
+ if RubyBuildManager.validate_tag(suggested_tag)
164
+ say '✅ Suggested tag is valid', :green
165
+ else
166
+ say '⚠️ Suggested tag not found in ruby-build definitions', :yellow
167
+ say 'You may need to update the cache or use a different tag', :white
168
+ end
169
+ rescue StandardError => e
170
+ say "❌ Failed to suggest tag: #{e.message}", :red
171
+ say 'Try updating the cache: serialbench ruby-build update', :white
172
+ exit 1
173
+ end
174
+
175
+ desc 'cache-info', 'Show information about the Ruby-Build definitions cache'
176
+ long_desc <<~DESC
177
+ Display information about the local Ruby-Build definitions cache,
178
+ including location, age, and update status.
179
+
180
+ Examples:
181
+ serialbench ruby-build cache-info
182
+ DESC
183
+ def cache_info
184
+ if RubyBuildManager.cache_exists?
185
+ cache_age = RubyBuildManager.cache_age
186
+ definitions_count = RubyBuildManager.list_definitions.length
187
+
188
+ say 'Ruby-Build Cache Information:', :green
189
+ say '=' * 40, :green
190
+ say "Location: #{RubyBuildManager::CACHE_FILE}", :cyan
191
+ say "Definitions: #{definitions_count}", :cyan
192
+ say "Age: #{format_cache_age(cache_age)}", :cyan
193
+ say 'Status: ✅ Available', :green
194
+
195
+ if cache_age > 7 * 24 * 60 * 60 # 7 days
196
+ say "\n💡 Cache is older than 7 days, consider updating:", :yellow
197
+ say ' serialbench ruby-build update', :white
198
+ end
199
+ else
200
+ say 'Ruby-Build Cache Information:', :green
201
+ say '=' * 40, :green
202
+ say "Location: #{RubyBuildManager::CACHE_FILE}", :cyan
203
+ say 'Status: ❌ Not found', :red
204
+ say "\n📥 Update the cache first:", :yellow
205
+ say ' serialbench ruby-build update', :white
206
+ end
207
+ end
208
+
209
+ private
210
+
211
+ def format_cache_age(seconds)
212
+ days = (seconds / (24 * 60 * 60)).to_i
213
+ hours = ((seconds % (24 * 60 * 60)) / (60 * 60)).to_i
214
+
215
+ if days.positive?
216
+ "#{days} day#{'s' if days != 1}, #{hours} hour#{'s' if hours != 1}"
217
+ elsif hours.positive?
218
+ "#{hours} hour#{'s' if hours != 1}"
219
+ else
220
+ 'less than 1 hour'
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
@@ -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