serialbench 0.8.1 → 0.9.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/scripts/format-status.sh +21 -0
- data/.github/scripts/push-to-data.sh +24 -10
- data/.github/workflows/benchmark-autoheal.yml +59 -0
- data/.github/workflows/benchmark.yml +61 -1
- data/.github/workflows/release.yml +3 -0
- data/CONTEXT.md +1 -0
- data/Gemfile +3 -1
- data/docs/adr/0007-data-repo-checkpoint-ledger.md +14 -0
- data/lib/serialbench/benchmark_runner.rb +9 -2
- data/lib/serialbench/serializers/json/yeptris_serializer.rb +48 -0
- data/lib/serialbench/serializers/toml/teptris_serializer.rb +48 -0
- data/lib/serialbench/serializers/yaml/yeptris_serializer.rb +57 -0
- data/lib/serialbench/serializers.rb +9 -3
- data/lib/serialbench/version.rb +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 814cb0f6d767b00d0b936a40e6d9cdc45ddb185fafd8c6149e67a78454f5cbdd
|
|
4
|
+
data.tar.gz: cf269293728af5a4eb3f479af7ed3e41204307e6953c04f8fc08298b4e45473f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 55385f1d7ad647b3e6e11d066276a6f8369f8d218571ecf19c01e8d71af9b8c62c126a980b65cb54c67bf989e22bb278d1c4b659dde046373f95dced12007b09
|
|
7
|
+
data.tar.gz: 8e9e38c807063136a6c97fcae14c24575849d044c61660edc85bb9ccdc7dc1ba1f19081cd9a43cd968934731d3c98aa40b28f633f1b84aedb128738fac7ca370
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Exit 0 if this leg's format results are already in the data repo, 1 if not.
|
|
3
|
+
# The data repo is the checkpoint ledger: a present file means the format was
|
|
4
|
+
# measured and pushed; reruns skip it and only fill what is missing.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
FMT="$1"
|
|
8
|
+
RUBY_VERSION="$2"
|
|
9
|
+
PLATFORM="$3"
|
|
10
|
+
DATA_TOKEN="${DATA_REPO_TOKEN:-}"
|
|
11
|
+
DATE="${DATA_RUN_DATE:-$(date -u +%Y-%m-%d)}"
|
|
12
|
+
|
|
13
|
+
[ -n "$DATA_TOKEN" ] || exit 1
|
|
14
|
+
|
|
15
|
+
TARGET="runs/${DATE}/${PLATFORM}-ruby-${RUBY_VERSION}.${FMT}.yaml"
|
|
16
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
17
|
+
-H "Authorization: Bearer ${DATA_TOKEN}" \
|
|
18
|
+
"https://api.github.com/repos/serialbench/data/contents/${TARGET}")
|
|
19
|
+
|
|
20
|
+
[ "$HTTP_CODE" = "200" ] && exit 0
|
|
21
|
+
exit 1
|
|
@@ -21,7 +21,9 @@ if [ ! -f "$RESULTS_FILE" ]; then
|
|
|
21
21
|
exit 0
|
|
22
22
|
fi
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
# The matrix start date (setup job output), so every push and rerun of one
|
|
25
|
+
# matrix lands in one dated directory; wall clock is only a fallback.
|
|
26
|
+
DATE="${DATA_RUN_DATE:-$(date -u +%Y-%m-%d)}"
|
|
25
27
|
TARGET_PATH="runs/${DATE}/${PLATFORM}-ruby-${RUBY_VERSION}.${FMT}.yaml"
|
|
26
28
|
CONTENT=$(base64 < "$RESULTS_FILE" | tr -d '\r\n')
|
|
27
29
|
|
|
@@ -37,19 +39,31 @@ HTTP_CODE=$(curl -s -o /tmp/data-push-resp.json -w "%{http_code}" -X PUT \
|
|
|
37
39
|
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
|
38
40
|
echo "Pushed ${TARGET_PATH}"
|
|
39
41
|
elif [ "$HTTP_CODE" = "422" ]; then
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
# File exists: fetch its sha and update. 409 (sha race when the branch
|
|
43
|
+
# moves between fetch and put) is retried with a fresh sha.
|
|
44
|
+
UPDATE_CODE=409
|
|
45
|
+
for TRY in 1 2 3; do
|
|
46
|
+
SHA=$(curl -sf \
|
|
47
|
+
-H "Authorization: Bearer ${DATA_TOKEN}" \
|
|
48
|
+
"https://api.github.com/repos/serialbench/data/contents/${TARGET_PATH}" \
|
|
49
|
+
| grep -oE '"sha":\s*"[a-f0-9]+"' | head -1 | grep -oE '[a-f0-9]{40}')
|
|
50
|
+
if [ -z "$SHA" ]; then
|
|
51
|
+
echo "::warning::422 but couldn't get sha for ${TARGET_PATH}"
|
|
52
|
+
break
|
|
53
|
+
fi
|
|
54
|
+
UPDATE_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
|
|
46
55
|
-H "Authorization: Bearer ${DATA_TOKEN}" \
|
|
47
56
|
-H "Accept: application/vnd.github+json" \
|
|
48
57
|
"https://api.github.com/repos/serialbench/data/contents/${TARGET_PATH}" \
|
|
49
|
-
-d "{\"message\": \"${PLATFORM} ruby-${RUBY_VERSION} ${FMT} (update)\", \"content\": \"${CONTENT}\", \"sha\": \"${SHA}\", \"branch\": \"main\"}"
|
|
58
|
+
-d "{\"message\": \"${PLATFORM} ruby-${RUBY_VERSION} ${FMT} (update)\", \"content\": \"${CONTENT}\", \"sha\": \"${SHA}\", \"branch\": \"main\"}")
|
|
59
|
+
[ "$UPDATE_CODE" = "200" ] && break
|
|
60
|
+
echo "update attempt $TRY got $UPDATE_CODE — refetching sha"
|
|
61
|
+
sleep 2
|
|
62
|
+
done
|
|
63
|
+
if [ "$UPDATE_CODE" = "200" ]; then
|
|
50
64
|
echo "Updated existing ${TARGET_PATH}"
|
|
51
|
-
|
|
52
|
-
echo "::warning::
|
|
65
|
+
elif [ -n "$SHA" ]; then
|
|
66
|
+
echo "::warning::could not update ${TARGET_PATH} ($UPDATE_CODE)"
|
|
53
67
|
fi
|
|
54
68
|
else
|
|
55
69
|
echo "::warning::Push failed (${HTTP_CODE}): $(cat /tmp/data-push-resp.json | head -c 200)"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: benchmark-autoheal
|
|
2
|
+
|
|
3
|
+
# Reruns failed legs (runner evictions, OOM blips) of the latest benchmark
|
|
4
|
+
# matrix without human intervention. Capped: a run gets at most 5 attempts,
|
|
5
|
+
# and deliberately cancelled runs are never resurrected.
|
|
6
|
+
on:
|
|
7
|
+
# Heal immediately when a matrix completes (reliable event seam); the
|
|
8
|
+
# hourly cron below is only a backstop — GitHub drops cron ticks freely.
|
|
9
|
+
workflow_run:
|
|
10
|
+
workflows: [benchmark-weekly]
|
|
11
|
+
types: [completed]
|
|
12
|
+
schedule:
|
|
13
|
+
# hourly at :23 — off the busy top of the hour
|
|
14
|
+
- cron: '23 * * * *'
|
|
15
|
+
workflow_dispatch:
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
actions: write
|
|
19
|
+
|
|
20
|
+
concurrency:
|
|
21
|
+
group: benchmark-autoheal
|
|
22
|
+
cancel-in-progress: false
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
rerun-failed:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
timeout-minutes: 5
|
|
28
|
+
steps:
|
|
29
|
+
- name: Rerun failed benchmark legs (capped)
|
|
30
|
+
env:
|
|
31
|
+
GH_TOKEN: ${{ github.token }}
|
|
32
|
+
run: |
|
|
33
|
+
set -euo pipefail
|
|
34
|
+
RUN_ID=$(gh run list -R "$GITHUB_REPOSITORY" --workflow=benchmark.yml --limit 1 --json databaseId --jq '.[0].databaseId')
|
|
35
|
+
[ -n "$RUN_ID" ] || { echo "no benchmark run found"; exit 0; }
|
|
36
|
+
|
|
37
|
+
STATUS=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID" --jq '.status')
|
|
38
|
+
if [ "$STATUS" != "completed" ]; then
|
|
39
|
+
echo "latest run $RUN_ID still $STATUS — nothing to do"
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
CONCLUSION=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID" --jq '.conclusion // ""')
|
|
44
|
+
if [ "$CONCLUSION" != "failure" ]; then
|
|
45
|
+
echo "latest run concluded $CONCLUSION — nothing to rerun (cancelled runs are deliberate)"
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
ATTEMPT=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID" --jq '.run_attempt')
|
|
50
|
+
if [ "$ATTEMPT" -ge 5 ]; then
|
|
51
|
+
echo "attempt cap reached ($ATTEMPT) — leaving for humans"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
FAILED=$(gh run view "$RUN_ID" -R "$GITHUB_REPOSITORY" --json jobs --jq '[.jobs[] | select(.conclusion == "failure")] | length')
|
|
56
|
+
[ "$FAILED" -gt 0 ] || { echo "no failed legs"; exit 0; }
|
|
57
|
+
|
|
58
|
+
echo "rerunning $FAILED failed legs (attempt $ATTEMPT -> $((ATTEMPT + 1)))"
|
|
59
|
+
gh run rerun "$RUN_ID" -R "$GITHUB_REPOSITORY" --failed
|
|
@@ -3,7 +3,12 @@ name: benchmark-weekly
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
5
|
branches: [main]
|
|
6
|
-
|
|
6
|
+
paths-ignore:
|
|
7
|
+
- 'README.md'
|
|
8
|
+
- 'CONTEXT.md'
|
|
9
|
+
- 'docs/**'
|
|
10
|
+
- 'lib/serialbench/version.rb'
|
|
11
|
+
- '**.md'
|
|
7
12
|
schedule:
|
|
8
13
|
# Run benchmarks weekly on Sundays at 2 AM UTC
|
|
9
14
|
- cron: '0 2 * * 0'
|
|
@@ -20,20 +25,24 @@ concurrency:
|
|
|
20
25
|
jobs:
|
|
21
26
|
setup:
|
|
22
27
|
runs-on: ubuntu-latest
|
|
28
|
+
timeout-minutes: 5
|
|
23
29
|
outputs:
|
|
24
30
|
ruby-versions: ${{ steps.set-matrix.outputs.ruby-versions }}
|
|
25
31
|
platforms: ${{ steps.set-matrix.outputs.platforms }}
|
|
32
|
+
run-date: ${{ steps.set-matrix.outputs.run-date }}
|
|
26
33
|
steps:
|
|
27
34
|
- name: Set matrix configurations
|
|
28
35
|
id: set-matrix
|
|
29
36
|
run: |
|
|
30
37
|
echo 'ruby-versions=["3.2", "3.3", "3.4", "4.0"]' >> $GITHUB_OUTPUT
|
|
31
38
|
echo 'platforms=["ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-14", "macos-15", "macos-15-intel", "macos-26", "macos-26-intel", "windows-2022", "windows-2025", "windows-11-arm"]' >> $GITHUB_OUTPUT
|
|
39
|
+
echo "run-date=$(date -u +%Y-%m-%d)" >> $GITHUB_OUTPUT
|
|
32
40
|
|
|
33
41
|
# Cross-platform native benchmarks
|
|
34
42
|
benchmark:
|
|
35
43
|
runs-on: ${{ matrix.platform }}
|
|
36
44
|
needs: setup
|
|
45
|
+
timeout-minutes: 120
|
|
37
46
|
strategy:
|
|
38
47
|
fail-fast: false
|
|
39
48
|
matrix:
|
|
@@ -125,7 +134,13 @@ jobs:
|
|
|
125
134
|
shell: bash
|
|
126
135
|
env:
|
|
127
136
|
GITHUB_RUNNER_PLATFORM: ${{ matrix.platform }}
|
|
137
|
+
DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
|
|
138
|
+
DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
|
|
128
139
|
run: |
|
|
140
|
+
if [ "${{ github.run_attempt }}" -gt 1 ] && .github/scripts/format-status.sh json ${{ matrix.ruby-version }} ${{ matrix.platform }}; then
|
|
141
|
+
echo "json already in the data repo — skipping (gap-filling rerun)"
|
|
142
|
+
exit 0
|
|
143
|
+
fi
|
|
129
144
|
bundle exec serialbench environment execute \
|
|
130
145
|
config/environments/ci-ruby-${{ matrix.ruby-version }}.yml \
|
|
131
146
|
config/benchmarks/full-json.yml \
|
|
@@ -145,6 +160,7 @@ jobs:
|
|
|
145
160
|
continue-on-error: true
|
|
146
161
|
env:
|
|
147
162
|
DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
|
|
163
|
+
DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
|
|
148
164
|
run: .github/scripts/push-to-data.sh json ${{ matrix.ruby-version }} ${{ matrix.platform }}
|
|
149
165
|
shell: bash
|
|
150
166
|
|
|
@@ -153,7 +169,13 @@ jobs:
|
|
|
153
169
|
shell: bash
|
|
154
170
|
env:
|
|
155
171
|
GITHUB_RUNNER_PLATFORM: ${{ matrix.platform }}
|
|
172
|
+
DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
|
|
173
|
+
DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
|
|
156
174
|
run: |
|
|
175
|
+
if [ "${{ github.run_attempt }}" -gt 1 ] && .github/scripts/format-status.sh yaml ${{ matrix.ruby-version }} ${{ matrix.platform }}; then
|
|
176
|
+
echo "yaml already in the data repo — skipping (gap-filling rerun)"
|
|
177
|
+
exit 0
|
|
178
|
+
fi
|
|
157
179
|
bundle exec serialbench environment execute \
|
|
158
180
|
config/environments/ci-ruby-${{ matrix.ruby-version }}.yml \
|
|
159
181
|
config/benchmarks/full-yaml.yml \
|
|
@@ -173,6 +195,7 @@ jobs:
|
|
|
173
195
|
continue-on-error: true
|
|
174
196
|
env:
|
|
175
197
|
DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
|
|
198
|
+
DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
|
|
176
199
|
run: .github/scripts/push-to-data.sh yaml ${{ matrix.ruby-version }} ${{ matrix.platform }}
|
|
177
200
|
shell: bash
|
|
178
201
|
|
|
@@ -181,7 +204,13 @@ jobs:
|
|
|
181
204
|
shell: bash
|
|
182
205
|
env:
|
|
183
206
|
GITHUB_RUNNER_PLATFORM: ${{ matrix.platform }}
|
|
207
|
+
DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
|
|
208
|
+
DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
|
|
184
209
|
run: |
|
|
210
|
+
if [ "${{ github.run_attempt }}" -gt 1 ] && .github/scripts/format-status.sh toml ${{ matrix.ruby-version }} ${{ matrix.platform }}; then
|
|
211
|
+
echo "toml already in the data repo — skipping (gap-filling rerun)"
|
|
212
|
+
exit 0
|
|
213
|
+
fi
|
|
185
214
|
bundle exec serialbench environment execute \
|
|
186
215
|
config/environments/ci-ruby-${{ matrix.ruby-version }}.yml \
|
|
187
216
|
config/benchmarks/full-toml.yml \
|
|
@@ -201,6 +230,7 @@ jobs:
|
|
|
201
230
|
continue-on-error: true
|
|
202
231
|
env:
|
|
203
232
|
DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
|
|
233
|
+
DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
|
|
204
234
|
run: .github/scripts/push-to-data.sh toml ${{ matrix.ruby-version }} ${{ matrix.platform }}
|
|
205
235
|
shell: bash
|
|
206
236
|
|
|
@@ -211,7 +241,13 @@ jobs:
|
|
|
211
241
|
shell: bash
|
|
212
242
|
env:
|
|
213
243
|
GITHUB_RUNNER_PLATFORM: ${{ matrix.platform }}
|
|
244
|
+
DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
|
|
245
|
+
DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
|
|
214
246
|
run: |
|
|
247
|
+
if [ "${{ github.run_attempt }}" -gt 1 ] && .github/scripts/format-status.sh xml ${{ matrix.ruby-version }} ${{ matrix.platform }}; then
|
|
248
|
+
echo "xml already in the data repo — skipping (gap-filling rerun)"
|
|
249
|
+
exit 0
|
|
250
|
+
fi
|
|
215
251
|
bundle exec serialbench environment execute \
|
|
216
252
|
config/environments/ci-ruby-${{ matrix.ruby-version }}.yml \
|
|
217
253
|
config/benchmarks/full-xml.yml \
|
|
@@ -231,13 +267,37 @@ jobs:
|
|
|
231
267
|
continue-on-error: true
|
|
232
268
|
env:
|
|
233
269
|
DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
|
|
270
|
+
DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
|
|
234
271
|
run: .github/scripts/push-to-data.sh xml ${{ matrix.ruby-version }} ${{ matrix.platform }}
|
|
235
272
|
shell: bash
|
|
236
273
|
|
|
237
274
|
|
|
275
|
+
|
|
276
|
+
- name: Verify leg completeness
|
|
277
|
+
if: ${{ !cancelled() }}
|
|
278
|
+
shell: bash
|
|
279
|
+
env:
|
|
280
|
+
DATA_REPO_TOKEN: ${{ secrets.DATA_REPO_TOKEN }}
|
|
281
|
+
DATA_RUN_DATE: ${{ needs.setup.outputs.run-date }}
|
|
282
|
+
run: |
|
|
283
|
+
MISSING=""
|
|
284
|
+
for FMT in json yaml toml xml; do
|
|
285
|
+
if .github/scripts/format-status.sh "$FMT" "${{ matrix.ruby-version }}" "${{ matrix.platform }}"; then
|
|
286
|
+
echo "$FMT: in data repo"
|
|
287
|
+
else
|
|
288
|
+
echo "$FMT: MISSING from data repo"
|
|
289
|
+
MISSING="$MISSING $FMT"
|
|
290
|
+
fi
|
|
291
|
+
done
|
|
292
|
+
if [ -n "$MISSING" ]; then
|
|
293
|
+
echo "::error::formats never pushed:${MISSING} — failing the leg so autoheal fills the gap"
|
|
294
|
+
exit 1
|
|
295
|
+
fi
|
|
296
|
+
|
|
238
297
|
# Summary job
|
|
239
298
|
summary:
|
|
240
299
|
runs-on: ubuntu-latest
|
|
300
|
+
timeout-minutes: 5
|
|
241
301
|
needs: [setup, benchmark]
|
|
242
302
|
if: always()
|
|
243
303
|
steps:
|
data/CONTEXT.md
CHANGED
|
@@ -8,4 +8,5 @@
|
|
|
8
8
|
- **Environment**: where a leg executes — `local`, `docker`, or `asdf` (`kind` in the environment config). Dispatched through `Runners.for`.
|
|
9
9
|
- **Environment key**: `{platform}-ruby-{version}` (e.g. `macos-26-ruby-3.4`) — the site's identity for a platform×ruby pair.
|
|
10
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
|
+
- **Data push**: one Result Leg's results.yaml written to the data repo via the Contents API (atomic, per-format).
|
|
11
12
|
- **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.
|
data/Gemfile
CHANGED
|
@@ -12,7 +12,9 @@ unless Gem.win_platform? && RUBY_PLATFORM.include?('aarch64')
|
|
|
12
12
|
gem 'libxml-ruby'
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
gem 'leptris', '1.9.
|
|
15
|
+
gem 'leptris', '1.9.152.1' # precompiled platform gems bundle libleptris (darwin/linux/mingw)
|
|
16
|
+
gem 'yeptris', '0.2.0.1' # precompiled darwin/linux; no windows or darwin-intel gems yet
|
|
17
|
+
gem 'teptris', '0.1.0' # precompiled all platforms
|
|
16
18
|
gem 'benchmark' # Removed from stdlib in Ruby 4.0
|
|
17
19
|
gem 'base64' # Required for Ruby 3.4+
|
|
18
20
|
gem 'lutaml-model', '~> 0.7'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ADR-0007: The data repo is the checkpoint ledger
|
|
2
|
+
|
|
3
|
+
Date: 2026-09-02
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
A leg's format steps are continue-on-error, so a Benchmark step failing inside a green job was invisible: autoheal (job-level) never saw it, and the missing format file surfaced only as a coverage gap (19 of 184 missing on 2026-09-01). Rerunning whole green jobs to recover one format costs ~45 minutes of re-measurement per leg — deliberate duplicate work.
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
The data repo is the source of truth for what a leg has delivered:
|
|
10
|
+
1. Every leg ends with a completeness check that queries the data repo for all four of its format files; any miss fails the leg, making gaps visible to autoheal.
|
|
11
|
+
2. Benchmark steps skip a format whose file already exists — but only on rerun attempts (`github.run_attempt > 1`), so first attempts always measure fresh code.
|
|
12
|
+
3. Consequently autoheal's reruns are surgical: pushed formats skip in seconds and only the missing format is measured — eviction windows shrink from ~45 minutes to a single format's runtime.
|
|
13
|
+
|
|
14
|
+
Gaps that survive the 5-attempt cap surface as red runs, never as silent holes.
|
|
@@ -65,9 +65,12 @@ module Serialbench
|
|
|
65
65
|
return [] unless defined?(::MemoryProfiler)
|
|
66
66
|
|
|
67
67
|
run_benchmark_iteration('memory') do |serializer, format, size, data|
|
|
68
|
-
# Memory profiling for parsing
|
|
68
|
+
# Memory profiling for parsing. MemoryProfiler disables GC while
|
|
69
|
+
# reporting, so every profiled parse accumulates: ten large-document
|
|
70
|
+
# trees exhaust the 16GB windows runners. One parse fully captures
|
|
71
|
+
# a large document's allocation and retention profile.
|
|
69
72
|
report = ::MemoryProfiler.report do
|
|
70
|
-
|
|
73
|
+
profile_iterations(size).times { serializer.parse(data) }
|
|
71
74
|
end
|
|
72
75
|
|
|
73
76
|
result = Models::MemoryPerformance.new(
|
|
@@ -173,6 +176,10 @@ module Serialbench
|
|
|
173
176
|
end
|
|
174
177
|
end
|
|
175
178
|
|
|
179
|
+
def profile_iterations(size)
|
|
180
|
+
size.to_s == 'large' ? 1 : 10
|
|
181
|
+
end
|
|
182
|
+
|
|
176
183
|
def validate_operations!
|
|
177
184
|
unknown = (@benchmark_config.operations || []) - (OPERATIONS.keys + ['memory'])
|
|
178
185
|
return if unknown.empty?
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_json_serializer'
|
|
4
|
+
|
|
5
|
+
module Serialbench
|
|
6
|
+
module Serializers
|
|
7
|
+
module Json
|
|
8
|
+
# Yeptris JSON side: parsing only — no dump in 0.2.x
|
|
9
|
+
class YeptrisSerializer < BaseJsonSerializer
|
|
10
|
+
def name
|
|
11
|
+
'yeptris-json'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def parse(json_string)
|
|
15
|
+
require 'yeptris'
|
|
16
|
+
Yeptris::JSON.load(json_string)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def capabilities
|
|
20
|
+
Set.new(%i[dom parse])
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def available?
|
|
24
|
+
return @available if defined?(@available)
|
|
25
|
+
|
|
26
|
+
@available = begin
|
|
27
|
+
require 'yeptris'
|
|
28
|
+
Yeptris::JSON.load('{"probe":true}')
|
|
29
|
+
true
|
|
30
|
+
rescue StandardError, LoadError
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def version
|
|
36
|
+
return 'unknown' unless available?
|
|
37
|
+
|
|
38
|
+
require 'yeptris'
|
|
39
|
+
Yeptris::VERSION
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def library_require_name
|
|
43
|
+
'yeptris'
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_toml_serializer'
|
|
4
|
+
|
|
5
|
+
module Serialbench
|
|
6
|
+
module Serializers
|
|
7
|
+
module Toml
|
|
8
|
+
class TeptrisSerializer < BaseTomlSerializer
|
|
9
|
+
def name
|
|
10
|
+
'teptris'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def parse(toml_string)
|
|
14
|
+
require 'teptris'
|
|
15
|
+
Teptris::TOML.load(toml_string)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generate(object, _options = {})
|
|
19
|
+
require 'teptris'
|
|
20
|
+
Teptris::TOML.dump(object)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def available?
|
|
24
|
+
return @available if defined?(@available)
|
|
25
|
+
|
|
26
|
+
@available = begin
|
|
27
|
+
require 'teptris'
|
|
28
|
+
Teptris::TOML.load("probe = true\n")
|
|
29
|
+
true
|
|
30
|
+
rescue StandardError, LoadError
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def version
|
|
36
|
+
return 'unknown' unless available?
|
|
37
|
+
|
|
38
|
+
require 'teptris'
|
|
39
|
+
Teptris::VERSION
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def library_require_name
|
|
43
|
+
'teptris'
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_yaml_serializer'
|
|
4
|
+
|
|
5
|
+
module Serialbench
|
|
6
|
+
module Serializers
|
|
7
|
+
module Yaml
|
|
8
|
+
class YeptrisSerializer < BaseYamlSerializer
|
|
9
|
+
def name
|
|
10
|
+
'yeptris-yaml'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def parse(yaml_string)
|
|
14
|
+
require 'yeptris'
|
|
15
|
+
Yeptris::YAML.load(yaml_string)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generate(object, _options = {})
|
|
19
|
+
require 'yeptris'
|
|
20
|
+
Yeptris::YAML.dump(object)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def stream_parse(yaml_string, &block)
|
|
24
|
+
require 'yeptris'
|
|
25
|
+
Yeptris::YAML.load_stream(yaml_string).each { |doc| block.call(:document, doc) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def capabilities
|
|
29
|
+
super | Set.new(%i[streaming])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def available?
|
|
33
|
+
return @available if defined?(@available)
|
|
34
|
+
|
|
35
|
+
@available = begin
|
|
36
|
+
require 'yeptris'
|
|
37
|
+
Yeptris::YAML.load('probe: true')
|
|
38
|
+
true
|
|
39
|
+
rescue StandardError, LoadError
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def version
|
|
45
|
+
return 'unknown' unless available?
|
|
46
|
+
|
|
47
|
+
require 'yeptris'
|
|
48
|
+
Yeptris::VERSION
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def library_require_name
|
|
52
|
+
'yeptris'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -18,17 +18,20 @@ require_relative 'serializers/json/json_serializer'
|
|
|
18
18
|
require_relative 'serializers/json/oj_serializer'
|
|
19
19
|
require_relative 'serializers/json/yajl_serializer'
|
|
20
20
|
require_relative 'serializers/json/rapidjson_serializer'
|
|
21
|
+
require_relative 'serializers/json/yeptris_serializer'
|
|
21
22
|
|
|
22
23
|
# YAML Serializers
|
|
23
24
|
require_relative 'serializers/yaml/base_yaml_serializer'
|
|
24
25
|
require_relative 'serializers/yaml/psych_serializer'
|
|
25
26
|
require_relative 'serializers/yaml/syck_serializer'
|
|
27
|
+
require_relative 'serializers/yaml/yeptris_serializer'
|
|
26
28
|
|
|
27
29
|
# TOML Serializers
|
|
28
30
|
require_relative 'serializers/toml/base_toml_serializer'
|
|
29
31
|
require_relative 'serializers/toml/toml_rb_serializer'
|
|
30
32
|
require_relative 'serializers/toml/tomlib_serializer'
|
|
31
33
|
require_relative 'serializers/toml/tomlrb_serializer'
|
|
34
|
+
require_relative 'serializers/toml/teptris_serializer'
|
|
32
35
|
|
|
33
36
|
module Serialbench
|
|
34
37
|
module Serializers
|
|
@@ -46,16 +49,19 @@ module Serialbench
|
|
|
46
49
|
Json::JsonSerializer,
|
|
47
50
|
Json::OjSerializer,
|
|
48
51
|
Json::RapidjsonSerializer,
|
|
49
|
-
Json::YajlSerializer
|
|
52
|
+
Json::YajlSerializer,
|
|
53
|
+
Json::YeptrisSerializer
|
|
50
54
|
],
|
|
51
55
|
yaml: [
|
|
52
56
|
Yaml::PsychSerializer,
|
|
53
|
-
Yaml::SyckSerializer
|
|
57
|
+
Yaml::SyckSerializer,
|
|
58
|
+
Yaml::YeptrisSerializer
|
|
54
59
|
],
|
|
55
60
|
toml: [
|
|
56
61
|
Toml::TomlRbSerializer,
|
|
57
62
|
Toml::TomlibSerializer,
|
|
58
|
-
Toml::TomlrbSerializer
|
|
63
|
+
Toml::TomlrbSerializer,
|
|
64
|
+
Toml::TeptrisSerializer
|
|
59
65
|
]
|
|
60
66
|
}.freeze
|
|
61
67
|
|
data/lib/serialbench/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
@@ -256,7 +256,9 @@ executables:
|
|
|
256
256
|
extensions: []
|
|
257
257
|
extra_rdoc_files: []
|
|
258
258
|
files:
|
|
259
|
+
- ".github/scripts/format-status.sh"
|
|
259
260
|
- ".github/scripts/push-to-data.sh"
|
|
261
|
+
- ".github/workflows/benchmark-autoheal.yml"
|
|
260
262
|
- ".github/workflows/benchmark.yml"
|
|
261
263
|
- ".github/workflows/rake.yml"
|
|
262
264
|
- ".github/workflows/release.yml"
|
|
@@ -296,6 +298,7 @@ files:
|
|
|
296
298
|
- docs/adr/0004-explicit-workflow-steps.md
|
|
297
299
|
- docs/adr/0005-site-loader-stays-flat.md
|
|
298
300
|
- docs/adr/0006-operations-vocabulary.md
|
|
301
|
+
- docs/adr/0007-data-repo-checkpoint-ledger.md
|
|
299
302
|
- exe/serialbench
|
|
300
303
|
- lib/serialbench.rb
|
|
301
304
|
- lib/serialbench/benchmark_runner.rb
|
|
@@ -325,7 +328,9 @@ files:
|
|
|
325
328
|
- lib/serialbench/serializers/json/oj_serializer.rb
|
|
326
329
|
- lib/serialbench/serializers/json/rapidjson_serializer.rb
|
|
327
330
|
- lib/serialbench/serializers/json/yajl_serializer.rb
|
|
331
|
+
- lib/serialbench/serializers/json/yeptris_serializer.rb
|
|
328
332
|
- lib/serialbench/serializers/toml/base_toml_serializer.rb
|
|
333
|
+
- lib/serialbench/serializers/toml/teptris_serializer.rb
|
|
329
334
|
- lib/serialbench/serializers/toml/toml_rb_serializer.rb
|
|
330
335
|
- lib/serialbench/serializers/toml/tomlib_serializer.rb
|
|
331
336
|
- lib/serialbench/serializers/toml/tomlrb_serializer.rb
|
|
@@ -339,6 +344,7 @@ files:
|
|
|
339
344
|
- lib/serialbench/serializers/yaml/base_yaml_serializer.rb
|
|
340
345
|
- lib/serialbench/serializers/yaml/psych_serializer.rb
|
|
341
346
|
- lib/serialbench/serializers/yaml/syck_serializer.rb
|
|
347
|
+
- lib/serialbench/serializers/yaml/yeptris_serializer.rb
|
|
342
348
|
- lib/serialbench/test_data.rb
|
|
343
349
|
- lib/serialbench/version.rb
|
|
344
350
|
- lib/serialbench/yaml_validator.rb
|