serialbench 0.7.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ad3e527e89762ad59203e00e1034b019a3b8a369e9251c52b6a3f389e9771e3
4
- data.tar.gz: dd68a1741b3a45d9b0f945ac4e23f35b48589c00dca63849f1223657da7f66d4
3
+ metadata.gz: 55b58686ee445c84cbbf69588022d932a56c7014255567c19d3bc44c2ddbad53
4
+ data.tar.gz: 0353b87e4e5d5690a706e2ef3b2e3d96f85166092b098893c13b9024b56acf4e
5
5
  SHA512:
6
- metadata.gz: 17462212111e78b23dadc54c7527dadbd57f9e5c90803876c56a455285e612ded431cb0c77428f0f194ab1831579e99cdc9030bf134971c2c4fed56ff8fc5292
7
- data.tar.gz: 4f5e62c00d38f4790bf0cd1fe30da9ed0993bd175e5a995264c82dbf17ebf9311c232733d3b42519550a02775827ff1a055039caac3f912dc7604c6c7b461867
6
+ metadata.gz: fdf9e8086fa5e7960151717340df5f131679af2389bf4126801f9a2ab777fed04f3eba511d108e2a4fe2d3f9e47c723970a22911b20880a162d93fdf85f7ace5
7
+ data.tar.gz: 665f581b8491ce518a0602f76dfc7227ae51c14f343bbf650c6ee5e4b735b3d64676d4af3fd2b6e10d9eb16209ca9ec412763072183d1101cf6e63c40088a83d
@@ -23,7 +23,7 @@ fi
23
23
 
24
24
  DATE=$(date -u +%Y-%m-%d)
25
25
  TARGET_PATH="runs/${DATE}/${PLATFORM}-ruby-${RUBY_VERSION}.${FMT}.yaml"
26
- CONTENT=$(base64 < "$RESULTS_FILE")
26
+ CONTENT=$(base64 < "$RESULTS_FILE" | tr -d '\r\n')
27
27
 
28
28
  echo "Pushing ${TARGET_PATH} to serialbench/data..."
29
29
 
@@ -120,36 +120,6 @@ jobs:
120
120
  EOF
121
121
  shell: bash
122
122
 
123
- # Each format is an independent checkpoint with its own upload.
124
- # A failure in one format doesn't prevent the others from uploading.
125
- - name: Benchmark xml
126
- continue-on-error: true
127
- shell: bash
128
- env:
129
- GITHUB_RUNNER_PLATFORM: ${{ matrix.platform }}
130
- run: |
131
- bundle exec serialbench environment execute \
132
- config/environments/ci-ruby-${{ matrix.ruby-version }}.yml \
133
- config/benchmarks/full-xml.yml \
134
- "results/runs/ci-ruby-${{ matrix.ruby-version }}-${{ matrix.platform }}/xml"
135
-
136
- - name: Upload xml
137
- if: ${{ !cancelled() }}
138
- uses: actions/upload-artifact@v4
139
- with:
140
- name: benchmark-results-${{ matrix.platform }}-ruby-${{ matrix.ruby-version }}-xml
141
- path: results/runs/ci-ruby-${{ matrix.ruby-version }}-${{ matrix.platform }}/xml
142
- retention-days: 30
143
- if-no-files-found: warn
144
-
145
- - name: Push xml to data repo
146
- if: ${{ !cancelled() }}
147
- continue-on-error: true
148
- env:
149
- DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
150
- run: .github/scripts/push-to-data.sh xml ${{ matrix.ruby-version }} ${{ matrix.platform }}
151
- shell: bash
152
-
153
123
  - name: Benchmark json
154
124
  continue-on-error: true
155
125
  shell: bash
@@ -234,6 +204,37 @@ jobs:
234
204
  run: .github/scripts/push-to-data.sh toml ${{ matrix.ruby-version }} ${{ matrix.platform }}
235
205
  shell: bash
236
206
 
207
+ # Each format is an independent checkpoint with its own upload.
208
+ # A failure in one format doesn't prevent the others from uploading.
209
+ - name: Benchmark xml
210
+ continue-on-error: true
211
+ shell: bash
212
+ env:
213
+ GITHUB_RUNNER_PLATFORM: ${{ matrix.platform }}
214
+ run: |
215
+ bundle exec serialbench environment execute \
216
+ config/environments/ci-ruby-${{ matrix.ruby-version }}.yml \
217
+ config/benchmarks/full-xml.yml \
218
+ "results/runs/ci-ruby-${{ matrix.ruby-version }}-${{ matrix.platform }}/xml"
219
+
220
+ - name: Upload xml
221
+ if: ${{ !cancelled() }}
222
+ uses: actions/upload-artifact@v4
223
+ with:
224
+ name: benchmark-results-${{ matrix.platform }}-ruby-${{ matrix.ruby-version }}-xml
225
+ path: results/runs/ci-ruby-${{ matrix.ruby-version }}-${{ matrix.platform }}/xml
226
+ retention-days: 30
227
+ if-no-files-found: warn
228
+
229
+ - name: Push xml to data repo
230
+ if: ${{ !cancelled() }}
231
+ continue-on-error: true
232
+ env:
233
+ DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
234
+ run: .github/scripts/push-to-data.sh xml ${{ matrix.ruby-version }} ${{ matrix.platform }}
235
+ shell: bash
236
+
237
+
237
238
  # Summary job
238
239
  summary:
239
240
  runs-on: ubuntu-latest
data/CONTEXT.md ADDED
@@ -0,0 +1,11 @@
1
+ # Serialbench domain glossary
2
+
3
+ - **Adapter** (a.k.a. serializer): one Ruby serialization library under measurement (nokogiri, leptris, oj, psych, toml-rb…). Registered per format in `Serializers::Serializers::REGISTER`. Its interface is the **capability set** (`capabilities` → `Set<Symbol>`); the `features` hash export derives from it.
4
+ - **Format**: xml, json, yaml, toml. One adapter belongs to exactly one format; one format has several adapters.
5
+ - **Operation**: one measured workload — `parsing`, `generation`, `xpath`, `streaming` (the `OPERATIONS` table in `BenchmarkRunner`), plus `memory` (profiled separately). Config `operations:` uses exactly these names; absent = run all; unknown name = error.
6
+ - **Result Leg**: one platform × ruby × format benchmark execution (one process, one config file). Produces one `results.yaml`.
7
+ - **Data Run**: one dated directory in the data repo (`runs/YYYY-MM-DD/`), holding one YAML file per environment × format. Append-only; the site's aggregator takes the latest run's data per environment.
8
+ - **Environment**: where a leg executes — `local`, `docker`, or `asdf` (`kind` in the environment config). Dispatched through `Runners.for`.
9
+ - **Environment key**: `{platform}-ruby-{version}` (e.g. `macos-26-ruby-3.4`) — the site's identity for a platform×ruby pair.
10
+ - **Capability**: a symbol in an adapter's capability set (`:xpath`, `:sax`, `:stax`, `:generate`, `:namespaces`…). Benchmarks filter adapters by capability; the site renders them as the capability strip.
11
+ - **Fixture**: the generated test document for a format × size (`small`/`medium`/`large`), built by `Serialbench::TestData`. `test_data/{size}.{format}` files override the generated one.
@@ -0,0 +1,9 @@
1
+ # ADR-0001: Three-repo split with incremental per-format pushes
2
+
3
+ Date: 2026-08-28
4
+
5
+ ## Context
6
+ The monolith coupled benchmark code, result storage, and site rendering; every publish was a batch job whose failure lost everything.
7
+
8
+ ## Decision
9
+ Three repos: `serialbench/serialbench` (gem), `serialbench/data` (append-only YAML runs), `serialbench/serialbench.github.io` (Astro site). Each Result Leg pushes its file to data via the Contents API immediately and triggers a site rebuild; legs are `continue-on-error` so one eviction never blocks the others.
@@ -0,0 +1,9 @@
1
+ # ADR-0002: Docker and ASDF runners stay
2
+
3
+ Date: 2026-08-28
4
+
5
+ ## Context
6
+ An architecture round deleted the docker/asdf runners as "unused by CI" (only LocalRunner executes in GitHub Actions).
7
+
8
+ ## Decision
9
+ Restore and keep them. The owner's requirement: every platform remains benchmarkable through the gem, not just what current CI exercises. Round 4 then repaired the asdf dispatch so the runner is actually reachable (`Runners.for`). Do not re-suggest deleting runners.
@@ -0,0 +1,9 @@
1
+ # ADR-0003: The capability set is the single adapter interface
2
+
3
+ Date: 2026-08-29
4
+
5
+ ## Context
6
+ Adapters declared capabilities through 49 individual `supports_X?` predicates, a parallel `features` hash, and (after a partial migration) a `capabilities` Set — three interfaces that could and did contradict (nokogiri's features denied streaming it has; rexml declared xpath without implementing `xpath_query`).
7
+
8
+ ## Decision
9
+ One source of truth: `capabilities` returns a `Set<Symbol>`; adapters override with `super | Set.new(...)`; the per-format `features` export derives from it; benchmark selection filters via `supports?`. New capability = one symbol. Do not reintroduce per-capability predicate methods.
@@ -0,0 +1,9 @@
1
+ # ADR-0004: Benchmark workflow keeps 12 explicit format steps
2
+
3
+ Date: 2026-08-30
4
+
5
+ ## Context
6
+ benchmark.yml repeats a benchmark/upload/push trio per format; a composite action could collapse 12 steps to 4.
7
+
8
+ ## Decision
9
+ Decline. `continue-on-error` is a caller-side property a composite cannot carry; the independent-checkpoint semantics would need re-implementation; each explicit step is a grep-able checkpoint during incident triage; the pipeline had a fragile week and the duplication is the price of the isolation property. Revisit only if a fifth format arrives.
@@ -0,0 +1,9 @@
1
+ # ADR-0005: Site loader stays a flat store
2
+
3
+ Date: 2026-08-29 (reaffirmed 2026-08-30)
4
+
5
+ ## Context
6
+ The site's `loadBenchmarks` returns a flat `BenchmarkStore` that components re-shape; a domain-query loader (leaderboard()/availability()/trend()) was proposed twice.
7
+
8
+ ## Decision
9
+ Defer. After the redesign the data layer is small, single-purpose modules (parser/aggregator/trend/versions, 18 tests) and no consumer friction has appeared. Revisit when a second dashboard view forces its own query shape.
@@ -0,0 +1,9 @@
1
+ # ADR-0006: One operations vocabulary
2
+
3
+ Date: 2026-08-29
4
+
5
+ ## Context
6
+ Benchmark configs declared `parse`/`generate`; the runner's OPERATIONS table used `parsing`/`generation`; `xpath` was in production configs but not the model's legal values; the config's `operations:` list was never read.
7
+
8
+ ## Decision
9
+ The OPERATIONS keys plus `memory` are the only legal names, end to end (config values, model validation, runner dispatch). Absent key = run everything; unknown name = `ArgumentError` with guidance. lutaml-model caveat: `default:` on a collection merges into deserialized values, so the everything-default lives in the runner, not the model.
@@ -135,6 +135,11 @@ module Serialbench
135
135
  results << result if result
136
136
  rescue StandardError => e
137
137
  puts " #{format}/#{serializer.name}: ERROR - #{e.message}"
138
+ ensure
139
+ # Free the previous adapter's documents before the next one
140
+ # parses + profiles; without this, six adapters' trees coexist
141
+ # and windows runners OOM during the xml memory pass.
142
+ GC.start
138
143
  end
139
144
  end
140
145
  end
@@ -75,7 +75,7 @@ module Serialbench
75
75
  name: serializer_singleton.name,
76
76
  format: serializer_singleton.format.to_s,
77
77
  version: serializer_singleton.version,
78
- features: serializer_singleton.features
78
+ features: serializer_singleton.features.transform_keys(&:to_s)
79
79
  )
80
80
  end
81
81
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Serialbench
4
- VERSION = '0.7.0'
4
+ VERSION = '0.8.1'
5
5
  end
data/lib/serialbench.rb CHANGED
@@ -4,7 +4,6 @@ require_relative 'serialbench/version'
4
4
  require_relative 'serialbench/serializers'
5
5
  require_relative 'serialbench/benchmark_runner'
6
6
  require_relative 'serialbench/cli'
7
- require_relative 'serialbench/memory_profiler'
8
7
  require_relative 'serialbench/models'
9
8
 
10
9
  module Serialbench
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serialbench
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
@@ -264,6 +264,7 @@ files:
264
264
  - ".rspec"
265
265
  - ".rubocop.yml"
266
266
  - ".rubocop_todo.yml"
267
+ - CONTEXT.md
267
268
  - Gemfile
268
269
  - README.adoc
269
270
  - Rakefile
@@ -289,6 +290,12 @@ files:
289
290
  - docs/WEBSITE_COMPLETION_PLAN.md
290
291
  - docs/WINDOWS_LIBXML_FIX.md
291
292
  - docs/WINDOWS_SETUP.md
293
+ - docs/adr/0001-three-repo-split.md
294
+ - docs/adr/0002-runners-stay.md
295
+ - docs/adr/0003-capability-set.md
296
+ - docs/adr/0004-explicit-workflow-steps.md
297
+ - docs/adr/0005-site-loader-stays-flat.md
298
+ - docs/adr/0006-operations-vocabulary.md
292
299
  - exe/serialbench
293
300
  - lib/serialbench.rb
294
301
  - lib/serialbench/benchmark_runner.rb
@@ -298,8 +305,6 @@ files:
298
305
  - lib/serialbench/cli/environment_cli.rb
299
306
  - lib/serialbench/cli/ruby_build_cli.rb
300
307
  - lib/serialbench/cli/validate_cli.rb
301
- - lib/serialbench/config_manager.rb
302
- - lib/serialbench/memory_profiler.rb
303
308
  - lib/serialbench/models.rb
304
309
  - lib/serialbench/models/benchmark_config.rb
305
310
  - lib/serialbench/models/benchmark_result.rb
@@ -1,129 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'yaml'
4
- require 'ostruct'
5
- require_relative 'schema_validator'
6
-
7
- module Serialbench
8
- # Manages configuration loading and validation for Serialbench
9
- class ConfigManager
10
- class ConfigurationError < StandardError; end
11
-
12
- SCHEMA_PATH = File.join(__dir__, '../../docs/serialbench_config_schema.yaml')
13
-
14
- # Load and validate configuration from file
15
- def self.load_and_validate(config_path)
16
- new.load_and_validate(config_path)
17
- end
18
-
19
- def initialize
20
- @validator = SchemaValidator.new
21
- end
22
-
23
- # Load configuration file and validate against schema
24
- def load_and_validate(config_path)
25
- raise ConfigurationError, "Configuration file not found: #{config_path}" unless File.exist?(config_path)
26
-
27
- begin
28
- config_data = YAML.load_file(config_path)
29
- rescue Psych::SyntaxError => e
30
- raise ConfigurationError, "Invalid YAML syntax in #{config_path}: #{e.message}"
31
- rescue StandardError => e
32
- raise ConfigurationError, "Error reading configuration file #{config_path}: #{e.message}"
33
- end
34
-
35
- validate_config(config_data, config_path)
36
- normalize_config(config_data)
37
- end
38
-
39
- private
40
-
41
- # Validate configuration against schema
42
- def validate_config(config_data, _config_path)
43
- # For now, perform basic validation since we don't have a specific config schema validator
44
- validate_basic_config(config_data)
45
- end
46
-
47
- # Load the configuration schema
48
- def load_schema
49
- raise ConfigurationError, "Configuration schema not found: #{SCHEMA_PATH}" unless File.exist?(SCHEMA_PATH)
50
-
51
- begin
52
- YAML.load_file(SCHEMA_PATH)
53
- rescue StandardError => e
54
- raise ConfigurationError, "Error loading configuration schema: #{e.message}"
55
- end
56
- end
57
-
58
- # Normalize and convert configuration to structured object
59
- def normalize_config(config_data)
60
- config = OpenStruct.new(config_data)
61
-
62
- # Apply defaults
63
- config.output_dir ||= 'benchmark-results'
64
- config.benchmark_config ||= 'config/full.yml'
65
- config.auto_install = true if config.auto_install.nil?
66
-
67
- # Validate runtime-specific requirements
68
- case config.runtime
69
- when 'docker'
70
- validate_docker_config(config)
71
- when 'asdf'
72
- validate_asdf_config(config)
73
- else
74
- raise ConfigurationError, "Unknown runtime: #{config.runtime}"
75
- end
76
-
77
- config
78
- end
79
-
80
- # Validate Docker-specific configuration
81
- def validate_docker_config(config)
82
- raise ConfigurationError, "Docker runtime requires 'image_variants' to be specified" unless config.image_variants && !config.image_variants.empty?
83
-
84
- invalid_variants = config.image_variants - %w[slim alpine]
85
- return if invalid_variants.empty?
86
-
87
- raise ConfigurationError, "Invalid image variants: #{invalid_variants.join(', ')}. Valid variants: slim, alpine"
88
- end
89
-
90
- # Validate ASDF-specific configuration
91
- def validate_asdf_config(config)
92
- # Check if ASDF is available
93
- raise ConfigurationError, 'ASDF is not installed or not in PATH. Please install ASDF to use asdf runtime.' unless command_available?('asdf')
94
-
95
- # Validate Ruby version format for ASDF (should include patch version)
96
- config.ruby_versions.each do |version|
97
- raise ConfigurationError, "ASDF runtime requires full version numbers (e.g., '3.2.8'), got: #{version}" unless version.match?(/^\d+\.\d+\.\d+$/)
98
- end
99
- end
100
-
101
- # Basic configuration validation
102
- def validate_basic_config(config_data)
103
- # Check required fields
104
- required_fields = %w[runtime ruby_versions output_dir benchmark_config]
105
- required_fields.each do |field|
106
- raise ConfigurationError, "Missing required field: #{field}" unless config_data.key?(field)
107
- end
108
-
109
- # Validate runtime
110
- valid_runtimes = %w[docker asdf]
111
- unless valid_runtimes.include?(config_data['runtime'])
112
- raise ConfigurationError,
113
- "Invalid runtime: #{config_data['runtime']}. Valid runtimes: #{valid_runtimes.join(', ')}"
114
- end
115
-
116
- # Validate ruby_versions is an array
117
- raise ConfigurationError, 'ruby_versions must be an array' unless config_data['ruby_versions'].is_a?(Array)
118
-
119
- return unless config_data['ruby_versions'].empty?
120
-
121
- raise ConfigurationError, 'ruby_versions cannot be empty'
122
- end
123
-
124
- # Check if a command is available in PATH
125
- def command_available?(command)
126
- system("which #{command} > /dev/null 2>&1")
127
- end
128
- end
129
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Serialbench
4
- class MemoryProfiler
5
- def self.profile(&block)
6
- return yield unless defined?(::MemoryProfiler)
7
-
8
- ::MemoryProfiler.report(&block)
9
- end
10
-
11
- def self.available?
12
- require 'memory_profiler'
13
- defined?(::MemoryProfiler) ? true : false
14
- rescue LoadError
15
- false
16
- end
17
-
18
- def self.format_report(report)
19
- return 'Memory profiling not available' unless report
20
-
21
- {
22
- total_allocated: report.total_allocated,
23
- total_retained: report.total_retained,
24
- allocated_memory: report.total_allocated_memsize,
25
- retained_memory: report.total_retained_memsize,
26
- allocated_objects_by_gem: report.allocated_memory_by_gem,
27
- retained_objects_by_gem: report.retained_memory_by_gem
28
- }
29
- end
30
- end
31
- end