serialbench 0.1.3 → 0.3.1
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/scripts/push-to-data.sh +65 -0
- data/.github/workflows/benchmark.yml +134 -168
- data/.github/workflows/release.yml +68 -13
- data/Gemfile +2 -0
- data/README.adoc +5 -0
- data/config/benchmarks/full-json.yml +25 -0
- data/config/benchmarks/full-toml.yml +25 -0
- data/config/benchmarks/full-xml.yml +26 -0
- data/config/benchmarks/full-yaml.yml +25 -0
- data/config/benchmarks/full.yml +9 -4
- data/lib/serialbench/benchmark_runner.rb +20 -23
- data/lib/serialbench/cli/benchmark_cli.rb +1 -1
- data/lib/serialbench/cli/resultset_cli.rb +40 -0
- data/lib/serialbench/cli.rb +1 -8
- data/lib/serialbench/models/benchmark_result.rb +2 -0
- data/lib/serialbench/models/platform.rb +5 -2
- data/lib/serialbench/serializers/base_serializer.rb +14 -2
- data/lib/serialbench/serializers/json/rapidjson_serializer.rb +0 -4
- data/lib/serialbench/serializers/xml/base_xml_serializer.rb +6 -5
- data/lib/serialbench/serializers/xml/leptris_serializer.rb +174 -0
- data/lib/serialbench/serializers/xml/libxml_serializer.rb +9 -0
- data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +4 -0
- data/lib/serialbench/serializers/xml/oga_serializer.rb +8 -0
- data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +1 -3
- data/lib/serialbench/serializers/yaml/psych_serializer.rb +0 -4
- data/lib/serialbench/serializers.rb +3 -1
- data/lib/serialbench/site_generator.rb +56 -2
- data/lib/serialbench/version.rb +1 -1
- data/lib/serialbench.rb +1 -1
- metadata +9 -15
- data/lib/serialbench/templates/assets/css/benchmark_report.css +0 -535
- data/lib/serialbench/templates/assets/css/format_based.css +0 -474
- data/lib/serialbench/templates/assets/css/themes.css +0 -589
- data/lib/serialbench/templates/assets/js/chart_helpers.js +0 -411
- data/lib/serialbench/templates/assets/js/dashboard.js +0 -795
- data/lib/serialbench/templates/assets/js/navigation.js +0 -142
- data/lib/serialbench/templates/base.liquid +0 -49
- data/lib/serialbench/templates/format_based.liquid +0 -507
- data/lib/serialbench/templates/partials/chart_section.liquid +0 -4
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Per-format split of full.yml: each format runs in its own process so
|
|
2
|
+
# memory profiling of large documents cannot compound across formats
|
|
3
|
+
# (windows runners OOMed with all four formats in one process).
|
|
4
|
+
name: full-xml
|
|
5
|
+
|
|
6
|
+
data_sizes:
|
|
7
|
+
- small
|
|
8
|
+
- medium
|
|
9
|
+
- large
|
|
10
|
+
|
|
11
|
+
formats:
|
|
12
|
+
- xml
|
|
13
|
+
|
|
14
|
+
iterations:
|
|
15
|
+
small: 10
|
|
16
|
+
medium: 3
|
|
17
|
+
large: 1
|
|
18
|
+
|
|
19
|
+
operations:
|
|
20
|
+
- parse
|
|
21
|
+
- generate
|
|
22
|
+
- streaming
|
|
23
|
+
- xpath
|
|
24
|
+
- memory
|
|
25
|
+
|
|
26
|
+
warmup: 2
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Per-format split of full.yml: each format runs in its own process so
|
|
2
|
+
# memory profiling of large documents cannot compound across formats
|
|
3
|
+
# (windows runners OOMed with all four formats in one process).
|
|
4
|
+
name: full-yaml
|
|
5
|
+
|
|
6
|
+
data_sizes:
|
|
7
|
+
- small
|
|
8
|
+
- medium
|
|
9
|
+
- large
|
|
10
|
+
|
|
11
|
+
formats:
|
|
12
|
+
- yaml
|
|
13
|
+
|
|
14
|
+
iterations:
|
|
15
|
+
small: 10
|
|
16
|
+
medium: 3
|
|
17
|
+
large: 1
|
|
18
|
+
|
|
19
|
+
operations:
|
|
20
|
+
- parse
|
|
21
|
+
- generate
|
|
22
|
+
- streaming
|
|
23
|
+
- memory
|
|
24
|
+
|
|
25
|
+
warmup: 2
|
data/config/benchmarks/full.yml
CHANGED
|
@@ -15,15 +15,20 @@ formats:
|
|
|
15
15
|
- yaml
|
|
16
16
|
- toml
|
|
17
17
|
|
|
18
|
+
# Trimmed from 20/5/2 (warmup 3): a healthy leg took ~70 minutes at that
|
|
19
|
+
# setting, which both inflates weekly cost and leaves legs exposed to
|
|
20
|
+
# runner-eviction flakiness. 10/3/1 with warmup 2 halves runtime with
|
|
21
|
+
# acceptable added noise.
|
|
18
22
|
iterations:
|
|
19
|
-
small:
|
|
20
|
-
medium:
|
|
21
|
-
large:
|
|
23
|
+
small: 10
|
|
24
|
+
medium: 3
|
|
25
|
+
large: 1
|
|
22
26
|
|
|
23
27
|
operations:
|
|
24
28
|
- parse
|
|
25
29
|
- generate
|
|
26
30
|
- streaming
|
|
31
|
+
- xpath
|
|
27
32
|
- memory
|
|
28
33
|
|
|
29
|
-
warmup:
|
|
34
|
+
warmup: 2
|
|
@@ -24,6 +24,19 @@ module Serialbench
|
|
|
24
24
|
load_test_data
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Each operation maps to a lambda. Adding an operation = one entry.
|
|
28
|
+
OPERATIONS = {
|
|
29
|
+
'parsing' => ->(s, data) { s.parse(data) },
|
|
30
|
+
'generation' => ->(s, data) { s.generate(s.parse(data)) },
|
|
31
|
+
'xpath' => lambda { |s, data|
|
|
32
|
+
doc = s.parse(data)
|
|
33
|
+
s.xpath_query(doc, '//book')
|
|
34
|
+
s.xpath_query(doc, "//book[@id='101']")
|
|
35
|
+
s.xpath_query(doc, '//book[price > 30]/title')
|
|
36
|
+
},
|
|
37
|
+
'streaming' => ->(s, data) { s.stream_parse(data) { |_event, _data| } },
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
27
40
|
def run_all_benchmarks
|
|
28
41
|
puts 'Serialbench - Running comprehensive serialization performance tests'
|
|
29
42
|
puts '=' * 70
|
|
@@ -32,34 +45,18 @@ module Serialbench
|
|
|
32
45
|
puts "Test data sizes: #{@test_data.keys.join(', ')}"
|
|
33
46
|
puts
|
|
34
47
|
|
|
48
|
+
results = {}
|
|
49
|
+
OPERATIONS.each do |name, handler|
|
|
50
|
+
results[name.to_sym] = run_benchmark_type(name, name, &handler)
|
|
51
|
+
end
|
|
52
|
+
results[:memory] = run_memory_benchmarks
|
|
53
|
+
|
|
35
54
|
Models::BenchmarkResult.new(
|
|
36
55
|
serializers: Serializers.information,
|
|
37
|
-
|
|
38
|
-
generation: run_generation_benchmarks,
|
|
39
|
-
memory: run_memory_benchmarks,
|
|
40
|
-
streaming: run_streaming_benchmarks
|
|
56
|
+
**results
|
|
41
57
|
)
|
|
42
58
|
end
|
|
43
59
|
|
|
44
|
-
def run_parsing_benchmarks
|
|
45
|
-
run_benchmark_type('parsing', 'parse') do |serializer, data|
|
|
46
|
-
serializer.parse(data)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def run_generation_benchmarks
|
|
51
|
-
run_benchmark_type('generation', 'generation') do |serializer, data|
|
|
52
|
-
document = serializer.parse(data)
|
|
53
|
-
serializer.generate(document)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def run_streaming_benchmarks
|
|
58
|
-
run_benchmark_type('streaming', 'stream parse') do |serializer, data|
|
|
59
|
-
serializer.stream_parse(data) { |event, data| }
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
60
|
def run_memory_benchmarks
|
|
64
61
|
puts "\nRunning memory usage benchmarks..."
|
|
65
62
|
return [] unless defined?(::MemoryProfiler)
|
|
@@ -190,7 +190,7 @@ module Serialbench
|
|
|
190
190
|
say "🏗️ Generating HTML site for result: #{result_path}", :green
|
|
191
191
|
|
|
192
192
|
# Use the unified site generator for results
|
|
193
|
-
|
|
193
|
+
puts 'Site generation moved to serialbench.github.io'
|
|
194
194
|
|
|
195
195
|
say '✅ HTML site generated successfully!', :green
|
|
196
196
|
say "Site location: #{options[:output_dir]}", :cyan
|
|
@@ -213,6 +213,46 @@ module Serialbench
|
|
|
213
213
|
exit 1
|
|
214
214
|
end
|
|
215
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
|
+
|
|
216
256
|
desc 'list', 'List all available resultsets'
|
|
217
257
|
long_desc <<~DESC
|
|
218
258
|
List all resultsets in the results/sets/ directory.
|
data/lib/serialbench/cli.rb
CHANGED
|
@@ -4,9 +4,9 @@ require 'thor'
|
|
|
4
4
|
require_relative 'cli/base_cli'
|
|
5
5
|
require_relative 'cli/environment_cli'
|
|
6
6
|
require_relative 'cli/benchmark_cli'
|
|
7
|
-
require_relative 'cli/resultset_cli'
|
|
8
7
|
require_relative 'cli/ruby_build_cli'
|
|
9
8
|
require_relative 'cli/validate_cli'
|
|
9
|
+
require_relative 'cli/resultset_cli'
|
|
10
10
|
|
|
11
11
|
module Serialbench
|
|
12
12
|
# Main CLI entry point for the new object-oriented command structure
|
|
@@ -17,9 +17,6 @@ module Serialbench
|
|
|
17
17
|
desc 'benchmark SUBCOMMAND', 'Manage individual benchmark runs'
|
|
18
18
|
subcommand 'benchmark', Serialbench::Cli::BenchmarkCli
|
|
19
19
|
|
|
20
|
-
desc 'resultset SUBCOMMAND', 'Manage benchmark resultsets (collections of runs)'
|
|
21
|
-
subcommand 'resultset', Serialbench::Cli::ResultsetCli
|
|
22
|
-
|
|
23
20
|
desc 'ruby_build SUBCOMMAND', 'Manage Ruby-Build definitions for validation'
|
|
24
21
|
subcommand 'ruby_build', Serialbench::Cli::RubyBuildCli
|
|
25
22
|
|
|
@@ -45,7 +42,6 @@ module Serialbench
|
|
|
45
42
|
COMMANDS:
|
|
46
43
|
environment Manage benchmark environments (Docker, ASDF, Local)
|
|
47
44
|
benchmark Manage individual benchmark runs
|
|
48
|
-
resultset Manage benchmark resultsets (collections of runs)
|
|
49
45
|
ruby-build Manage Ruby-Build definitions for validation
|
|
50
46
|
version Show version information
|
|
51
47
|
help Show this help message
|
|
@@ -63,12 +59,9 @@ module Serialbench
|
|
|
63
59
|
serialbench benchmark execute my-benchmark.yml
|
|
64
60
|
|
|
65
61
|
# Create a result set for comparison
|
|
66
|
-
serialbench resultset create comparison-set
|
|
67
|
-
serialbench resultset add-result comparison-set results/my-benchmark
|
|
68
62
|
|
|
69
63
|
# Generate static sites
|
|
70
64
|
serialbench benchmark build-site results/my-benchmark
|
|
71
|
-
serialbench resultset build-site resultsets/comparison-set
|
|
72
65
|
|
|
73
66
|
For detailed help on any command, use:
|
|
74
67
|
serialbench COMMAND help
|
|
@@ -68,6 +68,7 @@ module Serialbench
|
|
|
68
68
|
attribute :generation, IterationPerformance, collection: true
|
|
69
69
|
attribute :memory, MemoryPerformance, collection: true
|
|
70
70
|
attribute :streaming, IterationPerformance, collection: true
|
|
71
|
+
attribute :xpath, IterationPerformance, collection: true
|
|
71
72
|
|
|
72
73
|
key_value do
|
|
73
74
|
map 'serializers', to: :serializers
|
|
@@ -75,6 +76,7 @@ module Serialbench
|
|
|
75
76
|
map 'generation', to: :generation
|
|
76
77
|
map 'memory', to: :memory
|
|
77
78
|
map 'streaming', to: :streaming
|
|
79
|
+
map 'xpath', to: :xpath
|
|
78
80
|
end
|
|
79
81
|
end
|
|
80
82
|
end
|
|
@@ -15,8 +15,11 @@ module Serialbench
|
|
|
15
15
|
class Platform < Lutaml::Model::Serializable
|
|
16
16
|
attribute :platform_string, :string
|
|
17
17
|
attribute :kind, :string, default: -> { 'local' }
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
# Fully qualified: lutaml-model 0.8+ instance-evals default
|
|
19
|
+
# lambdas, so bare detect_os/detect_arch (class methods) no
|
|
20
|
+
# longer resolve there.
|
|
21
|
+
attribute :os, :string, default: -> { Serialbench::Models::Platform.detect_os }
|
|
22
|
+
attribute :arch, :string, default: -> { Serialbench::Models::Platform.detect_arch }
|
|
20
23
|
attribute :ruby_version, :string, default: -> { RUBY_VERSION }
|
|
21
24
|
attribute :ruby_platform, :string, default: -> { RUBY_PLATFORM }
|
|
22
25
|
attribute :ruby_build_tag, :string
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
1
3
|
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'singleton'
|
|
@@ -33,9 +35,19 @@ module Serialbench
|
|
|
33
35
|
result
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
# Capabilities: a Set of symbols declaring what this adapter can do.
|
|
39
|
+
# Subclasses override to add symbols; the base handles every query.
|
|
40
|
+
# This replaces the 12+ individual supports_X? predicates.
|
|
41
|
+
def capabilities
|
|
42
|
+
Set.new(%i[dom parse generate])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def supports?(capability)
|
|
46
|
+
capabilities.include?(capability)
|
|
47
|
+
end
|
|
48
|
+
|
|
36
49
|
def supports_streaming?
|
|
37
|
-
|
|
38
|
-
false
|
|
50
|
+
supports?(:sax) || supports?(:streaming)
|
|
39
51
|
end
|
|
40
52
|
|
|
41
53
|
def require_library(library_name)
|
|
@@ -23,13 +23,14 @@ module Serialbench
|
|
|
23
23
|
generate(document, options)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# XML-specific features
|
|
26
|
+
# XML-specific features derive from the capability set
|
|
27
27
|
def features
|
|
28
28
|
{
|
|
29
|
-
xpath:
|
|
30
|
-
namespaces:
|
|
31
|
-
validation:
|
|
32
|
-
streaming:
|
|
29
|
+
xpath: supports?(:xpath),
|
|
30
|
+
namespaces: supports?(:namespaces),
|
|
31
|
+
validation: supports?(:validation),
|
|
32
|
+
streaming: supports?(:sax) || supports?(:streaming),
|
|
33
|
+
stax: supports?(:stax)
|
|
33
34
|
}
|
|
34
35
|
end
|
|
35
36
|
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_xml_serializer'
|
|
4
|
+
|
|
5
|
+
module Serialbench
|
|
6
|
+
module Serializers
|
|
7
|
+
module Xml
|
|
8
|
+
class LeptrisSerializer < BaseXmlSerializer
|
|
9
|
+
def name
|
|
10
|
+
'leptris'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def parse(xml_string)
|
|
14
|
+
require 'leptris'
|
|
15
|
+
require 'leptris/xml'
|
|
16
|
+
Leptris::XML.parse(xml_string)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def generate(data)
|
|
20
|
+
require 'leptris'
|
|
21
|
+
require 'leptris/xml'
|
|
22
|
+
if data.is_a?(Leptris::XML::Document) || data.is_a?(Leptris::XML::Node)
|
|
23
|
+
data.to_xml(indent: 2)
|
|
24
|
+
else
|
|
25
|
+
build_document_from_data(data).to_xml(indent: 2)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def parse_streaming(xml_string, &block)
|
|
30
|
+
require 'leptris'
|
|
31
|
+
require 'leptris/xml'
|
|
32
|
+
|
|
33
|
+
handler = StreamingHandler.new(&block)
|
|
34
|
+
parser = Leptris::XML::SAX::Parser.new(handler)
|
|
35
|
+
parser.parse_memory(xml_string)
|
|
36
|
+
handler.elements_processed
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def supports_streaming?
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def capabilities
|
|
44
|
+
super | Set.new(%i[xpath namespaces sax stax])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def xpath_query(document, expression)
|
|
48
|
+
require 'leptris'
|
|
49
|
+
require 'leptris/xml'
|
|
50
|
+
result = document.xpath(expression)
|
|
51
|
+
result.size
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# leptris-ruby >= 1.6 exposes Leptris::XML::Pull, a StAX-style
|
|
55
|
+
# cursor (one event per #next). Declared by gem version so the
|
|
56
|
+
# check never triggers the FFI library load.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def version
|
|
60
|
+
return 'unknown' unless available?
|
|
61
|
+
|
|
62
|
+
require 'leptris'
|
|
63
|
+
require 'leptris/xml'
|
|
64
|
+
Leptris::VERSION
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def library_require_name
|
|
68
|
+
'leptris'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The gem's require succeeds even when the libtaurus shared library is
|
|
72
|
+
# missing -- the FFI load happens lazily at first use. Probe with a
|
|
73
|
+
# real parse so "available" means "actually usable".
|
|
74
|
+
def available?
|
|
75
|
+
return @available if defined?(@available)
|
|
76
|
+
|
|
77
|
+
@available = begin
|
|
78
|
+
require 'leptris'
|
|
79
|
+
require 'leptris/xml'
|
|
80
|
+
Leptris::XML.parse('<probe/>')
|
|
81
|
+
true
|
|
82
|
+
rescue StandardError, LoadError
|
|
83
|
+
false
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def build_document_from_data(data, root_name = 'root')
|
|
90
|
+
document = Leptris::XML.parse('<root/>')
|
|
91
|
+
document.root.name = sanitize_element_name(root_name)
|
|
92
|
+
append_data(document.root, data)
|
|
93
|
+
document
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def append_data(element, data)
|
|
97
|
+
case data
|
|
98
|
+
when Hash
|
|
99
|
+
data.each do |key, value|
|
|
100
|
+
child = element.document.create_element(sanitize_element_name(key.to_s))
|
|
101
|
+
element.add_child(child)
|
|
102
|
+
append_data(child, value)
|
|
103
|
+
end
|
|
104
|
+
when Array
|
|
105
|
+
data.each do |item|
|
|
106
|
+
child = element.document.create_element('item')
|
|
107
|
+
element.add_child(child)
|
|
108
|
+
append_data(child, item)
|
|
109
|
+
end
|
|
110
|
+
else
|
|
111
|
+
element.content = data.to_s
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def sanitize_element_name(name)
|
|
116
|
+
sanitized = name.to_s.gsub(/[^a-zA-Z0-9_]/, '_')
|
|
117
|
+
sanitized = "element_#{sanitized}" if sanitized.empty? || sanitized =~ /\A\d/
|
|
118
|
+
sanitized
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# SAX handler counting elements, mirroring the Nokogiri adapter's
|
|
122
|
+
# StreamingHandler shape (Leptris SAX delivers attrs as [name, value]
|
|
123
|
+
# pairs, same as Nokogiri::XML::SAX). Plain duck-typed class --
|
|
124
|
+
# Leptris::XML::SAX::Parser needs no base class, and subclassing it
|
|
125
|
+
# would trigger the FFI library load at file-load time.
|
|
126
|
+
class StreamingHandler
|
|
127
|
+
attr_reader :elements_processed
|
|
128
|
+
|
|
129
|
+
def initialize(&block)
|
|
130
|
+
@block = block
|
|
131
|
+
@elements_processed = 0
|
|
132
|
+
@element_stack = []
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# No-op callbacks the parser invokes unconditionally (the base
|
|
136
|
+
# Leptris::XML::SAX::Document normally provides these).
|
|
137
|
+
def start_document; end
|
|
138
|
+
def end_document; end
|
|
139
|
+
def xmldecl(_version, _encoding, _standalone); end
|
|
140
|
+
def comment(_string); end
|
|
141
|
+
def processing_instruction(_name, _content); end
|
|
142
|
+
def start_prefix_mapping(_prefix, _uri); end
|
|
143
|
+
def end_prefix_mapping(_prefix); end
|
|
144
|
+
def warning(_string); end
|
|
145
|
+
def error(_message, _line = 0, _column = 0); end
|
|
146
|
+
|
|
147
|
+
def start_element(name, attrs = [])
|
|
148
|
+
@elements_processed += 1
|
|
149
|
+
@element_stack.push({ name: name, attributes: Hash[attrs], children: [], text: '' })
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def end_element(_name)
|
|
153
|
+
element = @element_stack.pop
|
|
154
|
+
if @element_stack.empty?
|
|
155
|
+
@block&.call(element)
|
|
156
|
+
else
|
|
157
|
+
@element_stack.last[:children] << element
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def characters(string)
|
|
162
|
+
return if string.strip.empty?
|
|
163
|
+
|
|
164
|
+
@element_stack.last[:text] += string if @element_stack.any?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def cdata_block(string)
|
|
168
|
+
@element_stack.last[:text] += string if @element_stack.any?
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -10,6 +10,7 @@ require_relative 'serializers/xml/ox_serializer'
|
|
|
10
10
|
require_relative 'serializers/xml/nokogiri_serializer'
|
|
11
11
|
require_relative 'serializers/xml/oga_serializer'
|
|
12
12
|
require_relative 'serializers/xml/libxml_serializer'
|
|
13
|
+
require_relative 'serializers/xml/leptris_serializer'
|
|
13
14
|
|
|
14
15
|
# JSON Serializers
|
|
15
16
|
require_relative 'serializers/json/base_json_serializer'
|
|
@@ -38,7 +39,8 @@ module Serialbench
|
|
|
38
39
|
Xml::OxSerializer,
|
|
39
40
|
Xml::NokogiriSerializer,
|
|
40
41
|
Xml::OgaSerializer,
|
|
41
|
-
Xml::LibxmlSerializer
|
|
42
|
+
Xml::LibxmlSerializer,
|
|
43
|
+
Xml::LeptrisSerializer
|
|
42
44
|
],
|
|
43
45
|
json: [
|
|
44
46
|
Json::JsonSerializer,
|
|
@@ -30,6 +30,48 @@ module Serialbench
|
|
|
30
30
|
generator.generate_site
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def self.resultset_payload(resultset)
|
|
34
|
+
new(output_path: '/dev/null', resultset: resultset).resultset_payload
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.export_raw_data(resultset, data_dir)
|
|
38
|
+
new(output_path: '/dev/null', resultset: resultset).export_raw_data_to(data_dir)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resultset_payload
|
|
42
|
+
transform_resultset_for_dashboard(@resultset)
|
|
43
|
+
.merge('libraries' => libraries_information(@resultset))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Static capability matrix (adapter declarations) plus the versions
|
|
47
|
+
# actually measured in this resultset.
|
|
48
|
+
def libraries_information(resultset)
|
|
49
|
+
versions = {}
|
|
50
|
+
resultset.results.each do |result|
|
|
51
|
+
serializers = result.benchmark_result.serializers || []
|
|
52
|
+
serializers.each { |si| versions[si.name] ||= si.version }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
Serialbench::Serializers.all.filter_map do |adapter|
|
|
56
|
+
next if adapter.name == 'syck'
|
|
57
|
+
|
|
58
|
+
features = adapter.features
|
|
59
|
+
next if features.is_a?(Array)
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
'name' => adapter.name,
|
|
63
|
+
'format' => adapter.format.to_s,
|
|
64
|
+
'version' => versions[adapter.name] || 'unknown',
|
|
65
|
+
'features' => features.transform_values { |v| v == true }
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def export_raw_data_to(data_dir)
|
|
71
|
+
FileUtils.mkdir_p(data_dir)
|
|
72
|
+
export_resultset(@resultset, data_dir)
|
|
73
|
+
end
|
|
74
|
+
|
|
33
75
|
def generate_site
|
|
34
76
|
target_name = @result ? @result.environment_config.name : @resultset.name
|
|
35
77
|
|
|
@@ -140,6 +182,18 @@ module Serialbench
|
|
|
140
182
|
}
|
|
141
183
|
end
|
|
142
184
|
|
|
185
|
+
# Environment identity for the site: use the CI platform_string
|
|
186
|
+
# ("macos-26-ruby-3.4") when it is runner-shaped, so each runner label is
|
|
187
|
+
# its own environment; fall back to os-arch for other sources (docker,
|
|
188
|
+
# local runs, older resultsets).
|
|
189
|
+
def env_identity(result)
|
|
190
|
+
platform_string = result.platform.platform_string
|
|
191
|
+
ruby = result.platform.ruby_version
|
|
192
|
+
return platform_string if platform_string&.end_with?("-ruby-#{ruby}")
|
|
193
|
+
|
|
194
|
+
"#{result.platform.os}-#{result.platform.arch}-ruby-#{ruby}"
|
|
195
|
+
end
|
|
196
|
+
|
|
143
197
|
# Transform a ResultSet (collection of Results) into dashboard-compatible format
|
|
144
198
|
# Combines all results into a single dashboard structure
|
|
145
199
|
def transform_resultset_for_dashboard(resultset)
|
|
@@ -148,7 +202,7 @@ module Serialbench
|
|
|
148
202
|
|
|
149
203
|
resultset.results.each do |result|
|
|
150
204
|
# Create unique env key for this result
|
|
151
|
-
env_key =
|
|
205
|
+
env_key = env_identity(result)
|
|
152
206
|
|
|
153
207
|
# Merge this result's data into combined_results
|
|
154
208
|
result_combined = build_combined_results(result, env_key)
|
|
@@ -258,7 +312,7 @@ module Serialbench
|
|
|
258
312
|
|
|
259
313
|
def export_single_result(result, data_dir)
|
|
260
314
|
# Create filename based on platform info
|
|
261
|
-
env_key =
|
|
315
|
+
env_key = env_identity(result)
|
|
262
316
|
filename = "#{env_key}.yaml"
|
|
263
317
|
filepath = File.join(data_dir, filename)
|
|
264
318
|
|