serialbench 0.1.3 → 0.2.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +71 -51
  3. data/Gemfile +2 -0
  4. data/README.adoc +5 -0
  5. data/lib/serialbench/cli/resultset_cli.rb +40 -0
  6. data/lib/serialbench/models/platform.rb +5 -2
  7. data/lib/serialbench/serializers/json/rapidjson_serializer.rb +0 -4
  8. data/lib/serialbench/serializers/xml/base_xml_serializer.rb +6 -1
  9. data/lib/serialbench/serializers/xml/leptris_serializer.rb +167 -0
  10. data/lib/serialbench/serializers/xml/libxml_serializer.rb +9 -0
  11. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +9 -0
  12. data/lib/serialbench/serializers/xml/oga_serializer.rb +4 -0
  13. data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +1 -3
  14. data/lib/serialbench/serializers/yaml/psych_serializer.rb +0 -4
  15. data/lib/serialbench/serializers.rb +3 -1
  16. data/lib/serialbench/site_generator.rb +56 -2
  17. data/lib/serialbench/version.rb +1 -1
  18. data/site/.gitignore +5 -0
  19. data/site/README.md +32 -0
  20. data/site/astro.config.mjs +12 -0
  21. data/site/package-lock.json +6160 -0
  22. data/site/package.json +22 -0
  23. data/site/public/favicon.svg +4 -0
  24. data/site/scripts/gen-sample-data.mjs +154 -0
  25. data/site/src/components/AvailabilityMatrix.astro +61 -0
  26. data/site/src/components/CalibratedLeaderboard.vue +134 -0
  27. data/site/src/components/CitationBox.vue +50 -0
  28. data/site/src/components/DashboardConsole.vue +193 -0
  29. data/site/src/components/FeaturesTable.astro +97 -0
  30. data/site/src/components/MemoryPanel.vue +44 -0
  31. data/site/src/components/OpsChart.vue +135 -0
  32. data/site/src/config.ts +18 -0
  33. data/site/src/layouts/Layout.astro +85 -0
  34. data/site/src/lib/channels.ts +24 -0
  35. data/site/src/lib/dashboard.ts +269 -0
  36. data/site/src/pages/index.astro +44 -0
  37. data/site/src/pages/library/[slug].astro +128 -0
  38. data/site/src/pages/methodology.astro +55 -0
  39. data/site/src/styles/global.css +103 -0
  40. data/site/tsconfig.json +5 -0
  41. metadata +26 -2
@@ -0,0 +1,44 @@
1
+ ---
2
+ import Layout from '../layouts/Layout.astro';
3
+ import DashboardConsole from '../components/DashboardConsole.vue';
4
+ import AvailabilityMatrix from '../components/AvailabilityMatrix.astro';
5
+ import FeaturesTable from '../components/FeaturesTable.astro';
6
+ import payloadData from '../data/sample.json';
7
+ import type { Payload } from '../lib/dashboard';
8
+
9
+ const payload = payloadData as unknown as Payload;
10
+ const envCount = Object.keys(payload.environments).length;
11
+ const runDate = payload.metadata.generated_at.slice(0, 10);
12
+ ---
13
+
14
+ <Layout title="Serialbench — Ruby serialization benchmarks">
15
+ <section class="mb-10">
16
+ <p class="font-mono text-[10px] uppercase tracking-[0.14em] text-phosphor mb-3">Measurement console · run {runDate}</p>
17
+ <h1 class="text-3xl sm:text-4xl font-semibold tracking-tight max-w-3xl">
18
+ How fast can Ruby read and write your data?
19
+ </h1>
20
+ <p class="mt-3 text-inkdim max-w-2xl leading-relaxed">
21
+ Every XML, JSON, YAML and TOML serializer in the ecosystem, measured on the same workloads across
22
+ {envCount}
23
+ platform and Ruby combinations. Pick a format, pin a reference, and read the field.
24
+ </p>
25
+ </section>
26
+
27
+ <DashboardConsole client:load payload={payload} />
28
+
29
+ <AvailabilityMatrix payload={payload} />
30
+
31
+ <FeaturesTable payload={payload} />
32
+
33
+ <section class="mt-12 border border-line bg-panel px-5 py-5">
34
+ <h2 class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute mb-2">Raw data</h2>
35
+ <p class="text-sm text-inkdim max-w-2xl leading-relaxed">
36
+ Full results per environment and the complete resultset are exported as YAML alongside this site —
37
+ <span class="font-mono text-[13px] break-all">data/&lt;environment&gt;.yaml</span>
38
+ and
39
+ <span class="font-mono text-[13px] break-all">data/resultset.yaml</span>. Re-run any leg with
40
+ <span class="font-mono text-[13px] break-all">serialbench environment execute</span>
41
+ using the configs in the repository.
42
+ </p>
43
+ </section>
44
+ </Layout>
@@ -0,0 +1,128 @@
1
+ ---
2
+ import Layout from '../../layouts/Layout.astro';
3
+ import payloadData from '../../data/sample.json';
4
+ import type { Payload } from '../../lib/dashboard';
5
+ import {
6
+ availability,
7
+ environmentLabel,
8
+ environmentList,
9
+ formatIps,
10
+ formatsForSerializer,
11
+ libraryStats,
12
+ OPERATION_LABELS,
13
+ primaryEnvKey,
14
+ runnerShortLabel,
15
+ } from '../../lib/dashboard';
16
+ import { channelColor } from '../../lib/channels';
17
+ import { BASE } from '../../config';
18
+
19
+ export function getStaticPaths() {
20
+ const payload = payloadData as unknown as Payload;
21
+ return availability(payload).serializers.map((serializer) => ({
22
+ params: { slug: serializer },
23
+ }));
24
+ }
25
+
26
+ const { slug } = Astro.params;
27
+ const payload = payloadData as unknown as Payload;
28
+ const serializer = slug as string;
29
+
30
+ const { envKeys, cells } = availability(payload);
31
+ const envs = environmentList(payload);
32
+ const envKey = primaryEnvKey(payload);
33
+ const stats = libraryStats(payload, serializer, envKey);
34
+ const formats = formatsForSerializer(payload, serializer);
35
+ const covered = cells.get(serializer) ?? new Set<string>();
36
+ const rankOne = stats.filter((s) => s.rank === 1).length;
37
+ const color = channelColor(serializer);
38
+ const runDate = payload.metadata.generated_at.slice(0, 10);
39
+ ---
40
+
41
+ <Layout title={`${serializer} — Serialbench`}>
42
+ <section class="mb-8">
43
+ <p class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute mb-3">
44
+ <a href={BASE} class="hover:text-phosphor transition-colors">← Field overview</a>
45
+ </p>
46
+ <div class="flex items-center gap-3">
47
+ <span class="h-3 w-3 rounded-full" style={`background: ${color}`}></span>
48
+ <h1 class="text-3xl font-semibold tracking-tight font-mono">{serializer}</h1>
49
+ </div>
50
+ <p class="mt-2 text-inkdim">
51
+ {formats.map((f) => f.toUpperCase()).join(' / ')} serializer ·
52
+ measured on {covered.size} of {envKeys.length} environments ·
53
+ <span class="text-phosphor">#1 in {rankOne} of {stats.length} measurements</span>
54
+ <span class="text-inkmute"> · primary environment: {environmentLabel(envKey, payload.environments[envKey])}</span>
55
+ </p>
56
+ </section>
57
+
58
+ <section aria-label="Availability" class="mb-10 border border-line bg-panel px-4 py-4">
59
+ <p class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute mb-3">Measured environments</p>
60
+ <div class="flex flex-wrap gap-x-4 gap-y-2">
61
+ {
62
+ envs.map(({ key, env }) => (
63
+ <span class="font-mono text-[11px] flex items-center gap-1.5" title={environmentLabel(key, env)}>
64
+ {covered.has(key) ? (
65
+ <span class="h-2 w-2 rounded-full" style={`background: ${color}`} />
66
+ ) : (
67
+ <span class="h-2 w-2 rounded-full border border-line" />
68
+ )}
69
+ <span class={covered.has(key) ? 'text-inkdim' : 'text-inkmute line-through'}>{runnerShortLabel(key, env)}</span>
70
+ </span>
71
+ ))
72
+ }
73
+ </div>
74
+ </section>
75
+
76
+ <section aria-label="Measurements">
77
+ <p class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute mb-3">
78
+ Iterations per second on the primary environment · rank vs the field
79
+ </p>
80
+ <div class="overflow-x-auto border border-line bg-panel">
81
+ <table class="w-full border-collapse">
82
+ <thead>
83
+ <tr class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute">
84
+ <th class="text-left font-normal px-3 py-2 border-b border-line">Operation</th>
85
+ <th class="text-left font-normal px-3 py-2 border-b border-line">Input</th>
86
+ <th class="text-right font-normal px-3 py-2 border-b border-line">ips</th>
87
+ <th class="text-right font-normal px-3 py-2 border-b border-line">ms/op</th>
88
+ <th class="text-right font-normal px-3 py-2 border-b border-line">Rank</th>
89
+ <th class="text-left font-normal px-3 py-2 border-b border-line">vs best</th>
90
+ </tr>
91
+ </thead>
92
+ <tbody>
93
+ {
94
+ stats.map((s) => (
95
+ <tr>
96
+ <td class="px-3 py-1.5 border-b border-line/50 font-mono text-[12px]">
97
+ {OPERATION_LABELS[s.op as keyof typeof OPERATION_LABELS] ?? s.op}
98
+ </td>
99
+ <td class="px-3 py-1.5 border-b border-line/50 font-mono text-[12px] text-inkmute">{s.size}</td>
100
+ <td class="px-3 py-1.5 border-b border-line/50 font-mono text-[12px] text-right tabular">{formatIps(s.ips)}</td>
101
+ <td class="px-3 py-1.5 border-b border-line/50 font-mono text-[12px] text-right tabular text-inkdim">
102
+ {(1000 / s.ips).toFixed(s.ips < 10 ? 2 : 1)}
103
+ </td>
104
+ <td class="px-3 py-1.5 border-b border-line/50 font-mono text-[12px] text-right tabular">
105
+ <span class={s.rank === 1 ? 'text-phosphor' : 'text-inkdim'}>{s.rank}/{s.fieldSize}</span>
106
+ </td>
107
+ <td class="px-3 py-1.5 border-b border-line/50 font-mono text-[11px] text-inkmute whitespace-nowrap">
108
+ {s.ratioToBest == null ? '—' : s.rank === 1 ? 'best in field' : `${s.ratioToBest.toFixed(1)}× slower than ${s.bestSerializer}`}
109
+ </td>
110
+ </tr>
111
+ ))
112
+ }
113
+ </tbody>
114
+ </table>
115
+ </div>
116
+ <p class="font-mono text-[11px] text-inkmute mt-2">
117
+ Compare every library on every environment on the <a href={BASE} class="text-phosphor hover:underline">dashboard</a>.
118
+ </p>
119
+ </section>
120
+
121
+ <section class="mt-10 border border-line bg-panel px-4 py-3 flex flex-wrap items-center gap-4">
122
+ <span class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute shrink-0">Cite</span>
123
+ <p class="flex-1 min-w-64 font-mono text-[12px] leading-relaxed text-inkdim">
124
+ {serializer} measured on {environmentLabel(envKey, payload.environments[envKey])}, {formats.map((f) => f.toUpperCase()).join('/')}
125
+ workloads — serialbench, {runDate}. Full data: data/{envKey}.yaml.
126
+ </p>
127
+ </section>
128
+ </Layout>
@@ -0,0 +1,55 @@
1
+ ---
2
+ import Layout from '../layouts/Layout.astro';
3
+ import { SITE } from '../config';
4
+ ---
5
+
6
+ <Layout title="Methodology — Serialbench">
7
+ <section class="mb-10">
8
+ <p class="font-mono text-[10px] uppercase tracking-[0.14em] text-phosphor mb-3">Calibration</p>
9
+ <h1 class="text-3xl sm:text-4xl font-semibold tracking-tight">Methodology</h1>
10
+ <p class="mt-3 text-inkdim max-w-2xl leading-relaxed">
11
+ A benchmark you can't reproduce is marketing. This page documents exactly how serialbench
12
+ produces every number on this site.
13
+ </p>
14
+ </section>
15
+
16
+ <div class="grid gap-6 md:grid-cols-2 max-w-4xl">
17
+ <section class="border border-line bg-panel p-5">
18
+ <h2 class="font-mono text-[11px] uppercase tracking-[0.14em] text-phosphor mb-3">Workloads</h2>
19
+ <p class="text-sm text-inkdim leading-relaxed">
20
+ Each serializer parses, generates, and (where supported) streams the same documents at each
21
+ configured size. Operations run after warmup iterations; throughput is reported as
22
+ iterations per second, memory as allocated and retained bytes measured around the operation.
23
+ </p>
24
+ </section>
25
+
26
+ <section class="border border-line bg-panel p-5">
27
+ <h2 class="font-mono text-[11px] uppercase tracking-[0.14em] text-phosphor mb-3">Environments</h2>
28
+ <p class="text-sm text-inkdim leading-relaxed">
29
+ Results are collected per GitHub-hosted runner platform and Ruby version, then aggregated into
30
+ one resultset. Runners differ in CPU and memory — compare within an environment, not across
31
+ environments, unless you are comparing environments.
32
+ </p>
33
+ </section>
34
+
35
+ <section class="border border-line bg-panel p-5">
36
+ <h2 class="font-mono text-[11px] uppercase tracking-[0.14em] text-phosphor mb-3">Fairness limits</h2>
37
+ <p class="text-sm text-inkdim leading-relaxed">
38
+ Libraries that fail to load or lack an operation are skipped for that leg rather than counted
39
+ as zero. A dot missing from the availability matrix means no measurement exists — never assume
40
+ a score. Microbenchmarks measure these workloads, not yours.
41
+ </p>
42
+ </section>
43
+
44
+ <section class="border border-line bg-panel p-5">
45
+ <h2 class="font-mono text-[11px] uppercase tracking-[0.14em] text-phosphor mb-3">Reproduce</h2>
46
+ <p class="text-sm text-inkdim leading-relaxed">
47
+ Clone
48
+ <a class="text-phosphor hover:underline" href={SITE.repoUrl}>{SITE.repoUrl.replace("https://github.com/", "")}</a>,
49
+ then run
50
+ <span class="font-mono text-[13px]">bundle exec serialbench environment execute &lt;env&gt; &lt;benchmark&gt; &lt;out&gt;</span>.
51
+ The raw YAML next to every chart is the exact output of that command.
52
+ </p>
53
+ </section>
54
+ </div>
55
+ </Layout>
@@ -0,0 +1,103 @@
1
+ @import 'tailwindcss';
2
+
3
+ /*
4
+ * serialbench design tokens — instrument console.
5
+ * Semantic colors are runtime CSS vars so [data-theme] can retarget them;
6
+ * @theme inline exposes them to Tailwind utilities (bg-panel, text-inkdim…).
7
+ */
8
+ :root {
9
+ --surface: #0b101b;
10
+ --panel: #111a2b;
11
+ --panel-2: #16223a;
12
+ --line: #22314e;
13
+ --ink: #e9eef6;
14
+ --ink-dim: #a9b6c9;
15
+ --ink-mute: #6f7f99;
16
+ --phosphor: #5fd4e8;
17
+ --phosphor-ink: #071018;
18
+ --grid: rgba(95, 212, 232, 0.07);
19
+ --grid-strong: rgba(95, 212, 232, 0.14);
20
+ }
21
+
22
+ [data-theme='light'] {
23
+ --surface: #f3f5f9;
24
+ --panel: #ffffff;
25
+ --panel-2: #eaeef4;
26
+ --line: #d5dce7;
27
+ --ink: #16202e;
28
+ --ink-dim: #46566b;
29
+ --ink-mute: #75849d;
30
+ --phosphor: #0b7d96;
31
+ --phosphor-ink: #ffffff;
32
+ --grid: rgba(11, 125, 150, 0.08);
33
+ --grid-strong: rgba(11, 125, 150, 0.16);
34
+ }
35
+
36
+ @theme inline {
37
+ --color-surface: var(--surface);
38
+ --color-panel: var(--panel);
39
+ --color-panel2: var(--panel-2);
40
+ --color-line: var(--line);
41
+ --color-ink: var(--ink);
42
+ --color-inkdim: var(--ink-dim);
43
+ --color-inkmute: var(--ink-mute);
44
+ --color-phosphor: var(--phosphor);
45
+ --color-phosphorink: var(--phosphor-ink);
46
+ --font-sans: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif;
47
+ --font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', monospace;
48
+ }
49
+
50
+ html {
51
+ color-scheme: dark;
52
+ }
53
+ [data-theme='light'] {
54
+ color-scheme: light;
55
+ }
56
+
57
+ body {
58
+ background-color: var(--surface);
59
+ color: var(--ink);
60
+ font-family: var(--font-sans);
61
+ -webkit-font-smoothing: antialiased;
62
+ }
63
+
64
+ /* scope graticule: faint calibration grid used behind bars and panels */
65
+ .graticule {
66
+ background-image:
67
+ linear-gradient(to right, var(--grid) 1px, transparent 1px),
68
+ linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
69
+ background-size: 3rem 3rem;
70
+ }
71
+
72
+ .tabular {
73
+ font-variant-numeric: tabular-nums;
74
+ }
75
+
76
+ /* trace sweep-in for the leaderboard; disabled for reduced motion */
77
+ @keyframes trace-in {
78
+ from {
79
+ transform: scaleX(0);
80
+ }
81
+ to {
82
+ transform: scaleX(1);
83
+ }
84
+ }
85
+ .trace-in {
86
+ transform-origin: left center;
87
+ animation: trace-in 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
88
+ }
89
+ @media (prefers-reduced-motion: reduce) {
90
+ .trace-in {
91
+ animation: none;
92
+ }
93
+ }
94
+
95
+ ::selection {
96
+ background: var(--phosphor);
97
+ color: var(--phosphor-ink);
98
+ }
99
+
100
+ :focus-visible {
101
+ outline: 2px solid var(--phosphor);
102
+ outline-offset: 2px;
103
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "astro/tsconfigs/strict",
3
+ "include": [".astro/types.d.ts", "**/*"],
4
+ "exclude": ["dist"]
5
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serialbench
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-22 00:00:00.000000000 Z
11
+ date: 2026-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -337,6 +337,7 @@ files:
337
337
  - lib/serialbench/serializers/toml/tomlib_serializer.rb
338
338
  - lib/serialbench/serializers/toml/tomlrb_serializer.rb
339
339
  - lib/serialbench/serializers/xml/base_xml_serializer.rb
340
+ - lib/serialbench/serializers/xml/leptris_serializer.rb
340
341
  - lib/serialbench/serializers/xml/libxml_serializer.rb
341
342
  - lib/serialbench/serializers/xml/nokogiri_serializer.rb
342
343
  - lib/serialbench/serializers/xml/oga_serializer.rb
@@ -358,6 +359,29 @@ files:
358
359
  - lib/serialbench/version.rb
359
360
  - lib/serialbench/yaml_validator.rb
360
361
  - serialbench.gemspec
362
+ - site/.gitignore
363
+ - site/README.md
364
+ - site/astro.config.mjs
365
+ - site/package-lock.json
366
+ - site/package.json
367
+ - site/public/favicon.svg
368
+ - site/scripts/gen-sample-data.mjs
369
+ - site/src/components/AvailabilityMatrix.astro
370
+ - site/src/components/CalibratedLeaderboard.vue
371
+ - site/src/components/CitationBox.vue
372
+ - site/src/components/DashboardConsole.vue
373
+ - site/src/components/FeaturesTable.astro
374
+ - site/src/components/MemoryPanel.vue
375
+ - site/src/components/OpsChart.vue
376
+ - site/src/config.ts
377
+ - site/src/layouts/Layout.astro
378
+ - site/src/lib/channels.ts
379
+ - site/src/lib/dashboard.ts
380
+ - site/src/pages/index.astro
381
+ - site/src/pages/library/[slug].astro
382
+ - site/src/pages/methodology.astro
383
+ - site/src/styles/global.css
384
+ - site/tsconfig.json
361
385
  homepage: https://github.com/metanorma/serialbench
362
386
  licenses:
363
387
  - BSD-2-Clause