serialbench 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +13 -5
  3. data/.github/workflows/docker.yml +35 -9
  4. data/.github/workflows/rake.yml +15 -0
  5. data/Gemfile +2 -1
  6. data/README.adoc +267 -1129
  7. data/Rakefile +0 -55
  8. data/config/benchmarks/full.yml +29 -0
  9. data/config/benchmarks/short.yml +26 -0
  10. data/config/environments/asdf-ruby-3.2.yml +8 -0
  11. data/config/environments/asdf-ruby-3.3.yml +8 -0
  12. data/config/environments/docker-ruby-3.0.yml +9 -0
  13. data/config/environments/docker-ruby-3.1.yml +9 -0
  14. data/config/environments/docker-ruby-3.2.yml +9 -0
  15. data/config/environments/docker-ruby-3.3.yml +9 -0
  16. data/config/environments/docker-ruby-3.4.yml +9 -0
  17. data/docker/Dockerfile.alpine +33 -0
  18. data/docker/{Dockerfile.benchmark → Dockerfile.ubuntu} +4 -3
  19. data/docker/README.md +2 -2
  20. data/exe/serialbench +1 -1
  21. data/lib/serialbench/benchmark_runner.rb +261 -423
  22. data/lib/serialbench/cli/base_cli.rb +51 -0
  23. data/lib/serialbench/cli/benchmark_cli.rb +380 -0
  24. data/lib/serialbench/cli/environment_cli.rb +181 -0
  25. data/lib/serialbench/cli/resultset_cli.rb +215 -0
  26. data/lib/serialbench/cli/ruby_build_cli.rb +238 -0
  27. data/lib/serialbench/cli.rb +58 -601
  28. data/lib/serialbench/config_manager.rb +140 -0
  29. data/lib/serialbench/models/benchmark_config.rb +63 -0
  30. data/lib/serialbench/models/benchmark_result.rb +45 -0
  31. data/lib/serialbench/models/environment_config.rb +71 -0
  32. data/lib/serialbench/models/platform.rb +59 -0
  33. data/lib/serialbench/models/result.rb +53 -0
  34. data/lib/serialbench/models/result_set.rb +71 -0
  35. data/lib/serialbench/models/result_store.rb +108 -0
  36. data/lib/serialbench/models.rb +54 -0
  37. data/lib/serialbench/ruby_build_manager.rb +153 -0
  38. data/lib/serialbench/runners/asdf_runner.rb +296 -0
  39. data/lib/serialbench/runners/base.rb +32 -0
  40. data/lib/serialbench/runners/docker_runner.rb +142 -0
  41. data/lib/serialbench/serializers/base_serializer.rb +8 -16
  42. data/lib/serialbench/serializers/json/base_json_serializer.rb +4 -4
  43. data/lib/serialbench/serializers/json/json_serializer.rb +0 -2
  44. data/lib/serialbench/serializers/json/oj_serializer.rb +0 -2
  45. data/lib/serialbench/serializers/json/yajl_serializer.rb +0 -2
  46. data/lib/serialbench/serializers/toml/base_toml_serializer.rb +5 -3
  47. data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +0 -2
  48. data/lib/serialbench/serializers/toml/tomlib_serializer.rb +0 -2
  49. data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +56 -0
  50. data/lib/serialbench/serializers/xml/base_xml_serializer.rb +4 -9
  51. data/lib/serialbench/serializers/xml/libxml_serializer.rb +0 -2
  52. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +0 -2
  53. data/lib/serialbench/serializers/xml/oga_serializer.rb +0 -2
  54. data/lib/serialbench/serializers/xml/ox_serializer.rb +0 -2
  55. data/lib/serialbench/serializers/xml/rexml_serializer.rb +0 -2
  56. data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +5 -1
  57. data/lib/serialbench/serializers/yaml/syck_serializer.rb +59 -22
  58. data/lib/serialbench/serializers.rb +23 -6
  59. data/lib/serialbench/site_generator.rb +105 -0
  60. data/lib/serialbench/templates/assets/css/benchmark_report.css +535 -0
  61. data/lib/serialbench/templates/assets/css/format_based.css +526 -0
  62. data/lib/serialbench/templates/assets/css/themes.css +588 -0
  63. data/lib/serialbench/templates/assets/js/chart_helpers.js +381 -0
  64. data/lib/serialbench/templates/assets/js/dashboard.js +796 -0
  65. data/lib/serialbench/templates/assets/js/navigation.js +142 -0
  66. data/lib/serialbench/templates/base.liquid +49 -0
  67. data/lib/serialbench/templates/format_based.liquid +279 -0
  68. data/lib/serialbench/templates/partials/chart_section.liquid +4 -0
  69. data/lib/serialbench/version.rb +1 -1
  70. data/lib/serialbench.rb +2 -31
  71. data/serialbench.gemspec +4 -1
  72. metadata +86 -16
  73. data/config/ci.yml +0 -22
  74. data/config/full.yml +0 -30
  75. data/docker/run-benchmarks.sh +0 -356
  76. data/lib/serialbench/chart_generator.rb +0 -821
  77. data/lib/serialbench/result_formatter.rb +0 -182
  78. data/lib/serialbench/result_merger.rb +0 -1201
  79. data/lib/serialbench/serializers/xml/base_parser.rb +0 -69
  80. data/lib/serialbench/serializers/xml/libxml_parser.rb +0 -98
  81. data/lib/serialbench/serializers/xml/nokogiri_parser.rb +0 -111
  82. data/lib/serialbench/serializers/xml/oga_parser.rb +0 -85
  83. data/lib/serialbench/serializers/xml/ox_parser.rb +0 -64
  84. data/lib/serialbench/serializers/xml/rexml_parser.rb +0 -129
@@ -0,0 +1,215 @@
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
+ store = 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 RESULT_PATH RESULTSET_PATH', 'Add a run to a resultset'
53
+ long_desc <<~DESC
54
+ Add an existing benchmark run to a resultset.
55
+
56
+ RESULT_PATH should be the path to a run result directory
57
+ RESULTSET_PATH must be specified explicitly
58
+
59
+ Examples:
60
+ serialbench resultset add-result results/sets/performance-comparison results/runs/my-run-local-macos-arm64-ruby-3.3.8
61
+ serialbench resultset add-result results/sets/cross-platform-test results/runs/my-docker-run
62
+ DESC
63
+ def add_result(resultset_path, result_path)
64
+ resultset = Serialbench::Models::ResultSet.load(resultset_path)
65
+
66
+ # Validate that the run location exists
67
+ unless Dir.exist?(result_path)
68
+ say "Result directory not found: #{result_path}", :red
69
+ return
70
+ end
71
+
72
+ # Add run to resultset
73
+ resultset.add_result(result_path)
74
+ resultset.save(resultset_path)
75
+
76
+ say '✅ Added run to resultset', :green
77
+ say "Path: #{result_path}", :cyan
78
+ say "ResultSet: #{resultset_path}", :cyan
79
+ say "Total runs in set: #{resultset.results.count}", :white
80
+ rescue StandardError => e
81
+ say "Error adding run to resultset: #{e.message}", :red
82
+ exit 1
83
+ end
84
+
85
+ desc 'remove-result RESULTSET_PATH RESULT_PATH', 'Remove a run from a resultset'
86
+ long_desc <<~DESC
87
+ Remove a benchmark run from a resultset.
88
+
89
+ RESULTSET_PATH must be specified explicitly
90
+ RESULT_PATH is the path to the run result directory
91
+
92
+ Examples:
93
+ serialbench resultset remove-result results/sets/performance-comparison results/runs/my-run-local-macos-arm64-ruby-3.3.8
94
+ serialbench resultset remove-result results/sets/cross-platform-test results/runs/my-docker-run
95
+ DESC
96
+ def remove_result(resultset_path, result_path)
97
+ store = Serialbench::Models::ResultStore.default
98
+
99
+ # Find the resultset
100
+ resultset = Serialbench::Models::ResultSet.load(resultset_path)
101
+ if resultset.nil?
102
+ say "ResultSet '#{resultset_path}' not found", :red
103
+ exit 1
104
+ end
105
+
106
+ # Remove run from resultset
107
+ removed = resultset.remove_run(run_identifier)
108
+ unless removed
109
+ say "Run '#{run_identifier}' not found in resultset", :yellow
110
+ say 'Available runs in resultset:', :white
111
+ resultset.runs.each do |run_info|
112
+ say " - #{run_info[:name]}", :white
113
+ end
114
+ return
115
+ end
116
+
117
+ resultset.save
118
+
119
+ say '✅ Removed run from resultset', :green
120
+ say "Run: #{run_identifier}", :cyan
121
+ say "ResultSet: #{resultset_path}", :cyan
122
+ say "Remaining runs in set: #{resultset.runs.length}", :white
123
+ rescue StandardError => e
124
+ say "Error removing run from resultset: #{e.message}", :red
125
+ exit 1
126
+ end
127
+
128
+ desc 'build-site RESULTSET_PATH [OUTPUT_DIR]', 'Generate HTML site for a resultset'
129
+ long_desc <<~DESC
130
+ Generate an HTML site for a resultset (comparative analysis).
131
+
132
+ RESULTSET_PATH must be specified explicitly
133
+ OUTPUT_DIR defaults to _site/
134
+
135
+ Examples:
136
+ serialbench resultset build-site results/sets/performance-comparison
137
+ serialbench resultset build-site results/sets/cross-platform-test output/
138
+ DESC
139
+ option :output_dir, type: :string, default: '_site', desc: 'Output directory for generated site'
140
+ def build_site(resultset_path)
141
+ unless Dir.exist?(resultset_path)
142
+ say "ResultSet directory not found: #{resultset_path}", :red
143
+ say "Please create a resultset first using 'serialbench resultset create'", :white
144
+ exit 1
145
+ end
146
+
147
+ resultset = Serialbench::Models::ResultSet.load(resultset_path)
148
+
149
+ if resultset.results.empty?
150
+ say "ResultSet '#{resultset_path}' contains no runs", :yellow
151
+ say "Use 'serialbench resultset add-result' to add runs first", :white
152
+ return
153
+ end
154
+
155
+ say "🏗️ Generating HTML site for resultset: #{resultset_path}", :green
156
+ say "Runs in set: #{resultset.results.size}", :cyan
157
+
158
+ # Use the unified site generator for resultsets
159
+ Serialbench::SiteGenerator.generate_for_resultset(resultset, options[:output_dir])
160
+
161
+ say '✅ HTML site generated successfully!', :green
162
+ say "Site location: #{options[:output_dir]}", :cyan
163
+ say "Open: #{File.join(options[:output_dir], 'index.html')}", :white
164
+ rescue StandardError => e
165
+ say "Error generating site: #{e.message}", :red
166
+ say "Details: #{e.backtrace.first(3).join("\n")}", :red if options[:verbose]
167
+ exit 1
168
+ end
169
+
170
+ desc 'list', 'List all available resultsets'
171
+ long_desc <<~DESC
172
+ List all resultsets in the results/sets/ directory.
173
+
174
+ Shows resultset names, number of runs, and timestamps.
175
+ DESC
176
+ def list
177
+ ensure_results_directory
178
+
179
+ begin
180
+ store = Serialbench::Models::ResultStore.default
181
+ resultsets = store.find_resultsets
182
+
183
+ if resultsets.empty?
184
+ say 'No resultsets found', :yellow
185
+ say "Use 'serialbench resultset create' to create a resultset", :white
186
+ return
187
+ end
188
+
189
+ say 'Available ResultSets:', :green
190
+ say '=' * 50, :green
191
+
192
+ resultsets.each do |resultset|
193
+ say "📁 #{resultset.name}", :cyan
194
+ say " Runs: #{resultset.runs.length}", :white
195
+ say " Created: #{resultset.metadata[:timestamp]}", :white
196
+ say " Path: #{resultset.directory}", :white
197
+
198
+ if resultset.runs.any?
199
+ say ' Contains:', :white
200
+ resultset.runs.first(3).each do |run_info|
201
+ say " - #{run_info[:name]}", :white
202
+ end
203
+ say " ... and #{resultset.runs.length - 3} more" if resultset.runs.length > 3
204
+ end
205
+
206
+ say ''
207
+ end
208
+ rescue StandardError => e
209
+ say "Error listing resultsets: #{e.message}", :red
210
+ exit 1
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,238 @@
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
+
36
+ rescue StandardError => e
37
+ say "❌ Failed to update Ruby-Build definitions: #{e.message}", :red
38
+ exit 1
39
+ end
40
+ end
41
+
42
+ desc 'list [FILTER]', 'List available Ruby-Build definitions'
43
+ long_desc <<~DESC
44
+ List all available Ruby-Build definitions from the local cache.
45
+
46
+ Optionally filter the list by providing a filter string.
47
+
48
+ Examples:
49
+ serialbench ruby-build list # List all definitions
50
+ serialbench ruby-build list 3.3 # Filter by "3.3"
51
+ serialbench ruby-build list 3.2. # Filter by "3.2."
52
+ DESC
53
+ option :limit, type: :numeric, default: 50, desc: 'Maximum number of definitions to show'
54
+ def list(filter = nil)
55
+ begin
56
+ definitions = RubyBuildManager.list_definitions(filter: filter)
57
+
58
+ if definitions.empty?
59
+ if filter
60
+ say "No Ruby-Build definitions found matching '#{filter}'", :yellow
61
+ else
62
+ say 'No Ruby-Build definitions found in cache', :yellow
63
+ say 'Update the cache first: serialbench ruby-build update', :white
64
+ end
65
+ return
66
+ end
67
+
68
+ # Limit results if there are many
69
+ limited_definitions = definitions.first(options[:limit])
70
+
71
+ say "Ruby-Build Definitions#{filter ? " (filtered by '#{filter}')" : ''}:", :green
72
+ say '=' * 60, :green
73
+
74
+ limited_definitions.each do |definition|
75
+ say " #{definition}", :cyan
76
+ end
77
+
78
+ if definitions.length > options[:limit]
79
+ remaining = definitions.length - options[:limit]
80
+ say "\n... and #{remaining} more definitions", :yellow
81
+ say "Use --limit to show more results", :white
82
+ end
83
+
84
+ say "\nTotal: #{definitions.length} definitions", :white
85
+
86
+ rescue StandardError => e
87
+ say "❌ Failed to list Ruby-Build definitions: #{e.message}", :red
88
+ say 'Try updating the cache: serialbench ruby-build update', :white
89
+ exit 1
90
+ end
91
+ end
92
+
93
+ desc 'show TAG', 'Show details for a specific Ruby-Build definition'
94
+ long_desc <<~DESC
95
+ Show detailed information about a specific Ruby-Build definition.
96
+
97
+ Examples:
98
+ serialbench ruby-build show 3.3.8
99
+ serialbench ruby-build show 3.2.4
100
+ DESC
101
+ def show(tag)
102
+ begin
103
+ definition = RubyBuildManager.show_definition(tag)
104
+
105
+ say "Ruby-Build Definition: #{tag}", :green
106
+ say '=' * 40, :green
107
+ say "Tag: #{definition[:tag]}", :cyan
108
+ say "Available: #{definition[:available] ? '✅ Yes' : '❌ No'}", :cyan
109
+ say "Source: #{definition[:source]}", :cyan
110
+ say "Cache file: #{definition[:cache_file]}", :white
111
+
112
+ rescue StandardError => e
113
+ say "❌ #{e.message}", :red
114
+ say 'Available definitions: serialbench ruby-build list', :white
115
+ exit 1
116
+ end
117
+ end
118
+
119
+ desc 'validate TAG', 'Validate a Ruby-Build tag'
120
+ long_desc <<~DESC
121
+ Validate whether a Ruby-Build tag exists in the cached definitions.
122
+
123
+ Examples:
124
+ serialbench ruby-build validate 3.3.8
125
+ serialbench ruby-build validate 3.2.4
126
+ DESC
127
+ def validate(tag)
128
+ begin
129
+ valid = RubyBuildManager.validate_tag(tag)
130
+
131
+ if valid
132
+ say "✅ Ruby-Build tag '#{tag}' is valid", :green
133
+ else
134
+ say "❌ Ruby-Build tag '#{tag}' is not valid", :red
135
+
136
+ # Suggest similar tags
137
+ definitions = RubyBuildManager.list_definitions
138
+ similar = definitions.select { |d| d.include?(tag.split('.').first(2).join('.')) }.first(5)
139
+
140
+ if similar.any?
141
+ say "\n💡 Similar available tags:", :yellow
142
+ similar.each { |s| say " #{s}", :cyan }
143
+ end
144
+
145
+ exit 1
146
+ end
147
+
148
+ rescue StandardError => e
149
+ say "❌ Failed to validate tag: #{e.message}", :red
150
+ say 'Try updating the cache: serialbench ruby-build update', :white
151
+ exit 1
152
+ end
153
+ end
154
+
155
+ desc 'suggest', 'Suggest Ruby-Build tag for current Ruby version'
156
+ long_desc <<~DESC
157
+ Suggest an appropriate Ruby-Build tag based on the current Ruby version.
158
+
159
+ This is useful when creating local environments to get the correct
160
+ ruby_build_tag value.
161
+
162
+ Examples:
163
+ serialbench ruby-build suggest
164
+ DESC
165
+ def suggest
166
+ begin
167
+ current_ruby = RUBY_VERSION
168
+ suggested_tag = RubyBuildManager.suggest_current_ruby_tag
169
+
170
+ say "Current Ruby version: #{current_ruby}", :cyan
171
+ say "Suggested ruby_build_tag: #{suggested_tag}", :green
172
+
173
+ # Validate the suggestion
174
+ if RubyBuildManager.validate_tag(suggested_tag)
175
+ say "✅ Suggested tag is valid", :green
176
+ else
177
+ say "⚠️ Suggested tag not found in ruby-build definitions", :yellow
178
+ say "You may need to update the cache or use a different tag", :white
179
+ end
180
+
181
+ rescue StandardError => e
182
+ say "❌ Failed to suggest tag: #{e.message}", :red
183
+ say 'Try updating the cache: serialbench ruby-build update', :white
184
+ exit 1
185
+ end
186
+ end
187
+
188
+ desc 'cache-info', 'Show information about the Ruby-Build definitions cache'
189
+ long_desc <<~DESC
190
+ Display information about the local Ruby-Build definitions cache,
191
+ including location, age, and update status.
192
+
193
+ Examples:
194
+ serialbench ruby-build cache-info
195
+ DESC
196
+ def cache_info
197
+ if RubyBuildManager.cache_exists?
198
+ cache_age = RubyBuildManager.cache_age
199
+ definitions_count = RubyBuildManager.list_definitions.length
200
+
201
+ say 'Ruby-Build Cache Information:', :green
202
+ say '=' * 40, :green
203
+ say "Location: #{RubyBuildManager::CACHE_FILE}", :cyan
204
+ say "Definitions: #{definitions_count}", :cyan
205
+ say "Age: #{format_cache_age(cache_age)}", :cyan
206
+ say "Status: ✅ Available", :green
207
+
208
+ if cache_age > 7 * 24 * 60 * 60 # 7 days
209
+ say "\n💡 Cache is older than 7 days, consider updating:", :yellow
210
+ say " serialbench ruby-build update", :white
211
+ end
212
+ else
213
+ say 'Ruby-Build Cache Information:', :green
214
+ say '=' * 40, :green
215
+ say "Location: #{RubyBuildManager::CACHE_FILE}", :cyan
216
+ say "Status: ❌ Not found", :red
217
+ say "\n📥 Update the cache first:", :yellow
218
+ say " serialbench ruby-build update", :white
219
+ end
220
+ end
221
+
222
+ private
223
+
224
+ def format_cache_age(seconds)
225
+ days = (seconds / (24 * 60 * 60)).to_i
226
+ hours = ((seconds % (24 * 60 * 60)) / (60 * 60)).to_i
227
+
228
+ if days > 0
229
+ "#{days} day#{'s' if days != 1}, #{hours} hour#{'s' if hours != 1}"
230
+ elsif hours > 0
231
+ "#{hours} hour#{'s' if hours != 1}"
232
+ else
233
+ "less than 1 hour"
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end