serialbench 0.3.1 → 0.5.0
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 +0 -24
- data/.github/workflows/rake.yml +15 -18
- data/Gemfile +1 -1
- data/config/benchmarks/full-json.yml +2 -2
- data/config/benchmarks/full-toml.yml +2 -2
- data/config/benchmarks/full-xml.yml +2 -2
- data/config/benchmarks/full-yaml.yml +2 -2
- data/config/benchmarks/full.yml +2 -2
- data/config/benchmarks/short.yml +2 -2
- data/lib/serialbench/benchmark_runner.rb +21 -296
- data/lib/serialbench/cli/benchmark_cli.rb +8 -81
- data/lib/serialbench/cli.rb +0 -5
- data/lib/serialbench/models/benchmark_config.rb +1 -1
- data/lib/serialbench/models/result_store.rb +0 -38
- data/lib/serialbench/models.rb +0 -5
- data/lib/serialbench/serializers/base_serializer.rb +0 -4
- data/lib/serialbench/serializers/json/base_json_serializer.rb +8 -20
- data/lib/serialbench/serializers/json/json_serializer.rb +0 -16
- data/lib/serialbench/serializers/json/oj_serializer.rb +2 -17
- data/lib/serialbench/serializers/json/yajl_serializer.rb +2 -2
- data/lib/serialbench/serializers/toml/base_toml_serializer.rb +8 -29
- data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +0 -16
- data/lib/serialbench/serializers/toml/tomlib_serializer.rb +0 -4
- data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +2 -18
- data/lib/serialbench/serializers/xml/base_xml_serializer.rb +4 -16
- data/lib/serialbench/serializers/xml/leptris_serializer.rb +1 -5
- data/lib/serialbench/serializers/xml/libxml_serializer.rb +4 -9
- data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +3 -3
- data/lib/serialbench/serializers/xml/oga_serializer.rb +3 -5
- data/lib/serialbench/serializers/xml/ox_serializer.rb +2 -2
- data/lib/serialbench/serializers/xml/rexml_serializer.rb +5 -12
- data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +1 -9
- data/lib/serialbench/serializers/yaml/syck_serializer.rb +0 -8
- data/lib/serialbench/test_data.rb +315 -0
- data/lib/serialbench/version.rb +1 -1
- data/lib/serialbench.rb +2 -1
- data/serialbench.gemspec +1 -2
- metadata +6 -23
- data/.github/workflows/windows-debug.yml +0 -171
- data/lib/serialbench/cli/resultset_cli.rb +0 -301
- data/lib/serialbench/models/result_set.rb +0 -79
- data/lib/serialbench/site_generator.rb +0 -337
|
@@ -1,301 +0,0 @@
|
|
|
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 'export-data RESULTSET_PATH OUTPUT_JSON', 'Export a resultset as the site dashboard JSON payload'
|
|
217
|
-
long_desc <<~DESC
|
|
218
|
-
Write the aggregated dashboard payload (combined_results, environments,
|
|
219
|
-
metadata) for a resultset as JSON. This is the data contract consumed
|
|
220
|
-
by the Astro site in site/.
|
|
221
|
-
|
|
222
|
-
Optionally also export the raw per-environment YAML files and the
|
|
223
|
-
complete resultset YAML for download links.
|
|
224
|
-
|
|
225
|
-
Examples:
|
|
226
|
-
serialbench resultset export-data results/sets/weekly site/src/data/sample.json --raw-data-dir site/public/data
|
|
227
|
-
DESC
|
|
228
|
-
method_option 'raw-data-dir', type: :string,
|
|
229
|
-
desc: 'Also write per-environment YAML and resultset.yaml to this directory'
|
|
230
|
-
def export_data(resultset_path, output_json)
|
|
231
|
-
resultset = Serialbench::Models::ResultSet.load(resultset_path)
|
|
232
|
-
|
|
233
|
-
if resultset.results.empty?
|
|
234
|
-
say "ResultSet '#{resultset_path}' contains no runs", :yellow
|
|
235
|
-
say "Use 'serialbench resultset add-result' to add runs first", :white
|
|
236
|
-
exit 1
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
payload = Serialbench::SiteGenerator.resultset_payload(resultset)
|
|
240
|
-
output_dir = File.dirname(File.expand_path(output_json))
|
|
241
|
-
FileUtils.mkdir_p(output_dir)
|
|
242
|
-
File.write(output_json, JSON.pretty_generate(payload))
|
|
243
|
-
|
|
244
|
-
say "✅ Dashboard payload exported: #{output_json}", :green
|
|
245
|
-
say "Environments: #{payload['environments'].size}", :cyan
|
|
246
|
-
|
|
247
|
-
if options['raw-data-dir']
|
|
248
|
-
Serialbench::SiteGenerator.export_raw_data(resultset, options['raw-data-dir'])
|
|
249
|
-
say "📦 Raw YAML exported to: #{options['raw-data-dir']}", :cyan
|
|
250
|
-
end
|
|
251
|
-
rescue StandardError => e
|
|
252
|
-
say "Error exporting data: #{e.message}", :red
|
|
253
|
-
exit 1
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
desc 'list', 'List all available resultsets'
|
|
257
|
-
long_desc <<~DESC
|
|
258
|
-
List all resultsets in the results/sets/ directory.
|
|
259
|
-
|
|
260
|
-
Shows resultset names, number of runs, and timestamps.
|
|
261
|
-
DESC
|
|
262
|
-
def list
|
|
263
|
-
ensure_results_directory
|
|
264
|
-
|
|
265
|
-
begin
|
|
266
|
-
store = Serialbench::Models::ResultStore.default
|
|
267
|
-
resultsets = store.find_resultsets
|
|
268
|
-
|
|
269
|
-
if resultsets.empty?
|
|
270
|
-
say 'No resultsets found', :yellow
|
|
271
|
-
say "Use 'serialbench resultset create' to create a resultset", :white
|
|
272
|
-
return
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
say 'Available ResultSets:', :green
|
|
276
|
-
say '=' * 50, :green
|
|
277
|
-
|
|
278
|
-
resultsets.each do |resultset|
|
|
279
|
-
say "📁 #{resultset.name}", :cyan
|
|
280
|
-
say " Runs: #{resultset.runs.length}", :white
|
|
281
|
-
say " Created: #{resultset.metadata[:timestamp]}", :white
|
|
282
|
-
say " Path: #{resultset.directory}", :white
|
|
283
|
-
|
|
284
|
-
if resultset.runs.any?
|
|
285
|
-
say ' Contains:', :white
|
|
286
|
-
resultset.runs.first(3).each do |run_info|
|
|
287
|
-
say " - #{run_info[:name]}", :white
|
|
288
|
-
end
|
|
289
|
-
say " ... and #{resultset.runs.length - 3} more" if resultset.runs.length > 3
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
say ''
|
|
293
|
-
end
|
|
294
|
-
rescue StandardError => e
|
|
295
|
-
say "Error listing resultsets: #{e.message}", :red
|
|
296
|
-
exit 1
|
|
297
|
-
end
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
end
|
|
@@ -1,79 +0,0 @@
|
|
|
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
|
-
# 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
|
-
|
|
55
|
-
# Check if result already exists:
|
|
56
|
-
# If environment_config.created_at is identical;
|
|
57
|
-
# If platform.platform_string is identical;
|
|
58
|
-
# If benchmark_config.benchmark_name is identical;
|
|
59
|
-
|
|
60
|
-
duplicates = results.select do |r|
|
|
61
|
-
# Skip results with nil platform (defensive check)
|
|
62
|
-
next if r.platform.nil? || result.platform.nil?
|
|
63
|
-
|
|
64
|
-
r.platform.platform_string == result.platform.platform_string &&
|
|
65
|
-
r.environment_config.created_at == result.environment_config.created_at &&
|
|
66
|
-
r.benchmark_config.benchmark_name == result.benchmark_config.benchmark_name
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
raise ArgumentError, 'Result is already present in this resultset' if duplicates.any?
|
|
70
|
-
|
|
71
|
-
# Add the result to the resultset
|
|
72
|
-
results << result
|
|
73
|
-
self.updated_at = Time.now.utc.iso8601
|
|
74
|
-
|
|
75
|
-
result
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|