serialbench 0.2.0 → 0.4.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/push-to-data.sh +65 -0
- data/.github/workflows/benchmark.yml +98 -172
- data/.github/workflows/release.yml +68 -13
- data/Gemfile +1 -1
- 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 +32 -320
- data/lib/serialbench/cli/benchmark_cli.rb +1 -1
- data/lib/serialbench/cli.rb +0 -12
- data/lib/serialbench/models/benchmark_result.rb +2 -0
- data/lib/serialbench/models/result_store.rb +0 -38
- data/lib/serialbench/models.rb +0 -5
- data/lib/serialbench/serializers/base_serializer.rb +11 -3
- 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 +10 -26
- data/lib/serialbench/serializers/xml/leptris_serializer.rb +13 -10
- data/lib/serialbench/serializers/xml/libxml_serializer.rb +4 -9
- data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +4 -9
- data/lib/serialbench/serializers/xml/oga_serializer.rb +6 -4
- 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 +13 -59
- 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
- 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
- data/site/.gitignore +0 -5
- data/site/README.md +0 -32
- data/site/astro.config.mjs +0 -12
- data/site/package-lock.json +0 -6160
- data/site/package.json +0 -22
- data/site/public/favicon.svg +0 -4
- data/site/scripts/gen-sample-data.mjs +0 -154
- data/site/src/components/AvailabilityMatrix.astro +0 -61
- data/site/src/components/CalibratedLeaderboard.vue +0 -134
- data/site/src/components/CitationBox.vue +0 -50
- data/site/src/components/DashboardConsole.vue +0 -193
- data/site/src/components/FeaturesTable.astro +0 -97
- data/site/src/components/MemoryPanel.vue +0 -44
- data/site/src/components/OpsChart.vue +0 -135
- data/site/src/config.ts +0 -18
- data/site/src/layouts/Layout.astro +0 -85
- data/site/src/lib/channels.ts +0 -24
- data/site/src/lib/dashboard.ts +0 -269
- data/site/src/pages/index.astro +0 -44
- data/site/src/pages/library/[slug].astro +0 -128
- data/site/src/pages/methodology.astro +0 -55
- data/site/src/styles/global.css +0 -103
- data/site/tsconfig.json +0 -5
data/site/package.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "serialbench-site",
|
|
3
|
-
"type": "module",
|
|
4
|
-
"version": "0.1.0",
|
|
5
|
-
"private": true,
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "astro dev",
|
|
8
|
-
"build": "astro build",
|
|
9
|
-
"preview": "astro preview",
|
|
10
|
-
"generate:sample-data": "node scripts/gen-sample-data.mjs"
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"@astrojs/vue": "^7.0.2",
|
|
14
|
-
"@fontsource/ibm-plex-mono": "^5.3.0",
|
|
15
|
-
"@fontsource/ibm-plex-sans": "^5.3.0",
|
|
16
|
-
"@tailwindcss/vite": "^4.3.3",
|
|
17
|
-
"astro": "^7.2.4",
|
|
18
|
-
"chart.js": "^4.5.1",
|
|
19
|
-
"tailwindcss": "^4.3.3",
|
|
20
|
-
"vue": "^3.5.41"
|
|
21
|
-
}
|
|
22
|
-
}
|
data/site/public/favicon.svg
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
2
|
-
<rect width="24" height="24" rx="3" fill="#0b101b"/>
|
|
3
|
-
<path d="M4 15 L8 15 L10 8 L13 17 L15 11 L17 15 L20 15" stroke="#5fd4e8" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
-
</svg>
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// Generates src/data/sample.json in the exact shape the Ruby CLI's
|
|
2
|
-
// resultset export will produce: combined_results[op][size][format][serializer][envKey]
|
|
3
|
-
// plus environments{} and metadata{}. Numbers are anchored to the real
|
|
4
|
-
// 2026-08-23 local race (leptris 1.1.0, macOS arm64, Ruby 3.4.8) and scaled
|
|
5
|
-
// per environment with deterministic factors -- plausible, never random.
|
|
6
|
-
//
|
|
7
|
-
// In CI the real payload replaces this file before `astro build`.
|
|
8
|
-
|
|
9
|
-
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
10
|
-
import { dirname, join } from 'node:path';
|
|
11
|
-
import { fileURLToPath } from 'node:url';
|
|
12
|
-
|
|
13
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
const outPath = join(here, '..', 'src', 'data', 'sample.json');
|
|
15
|
-
|
|
16
|
-
const envs = [
|
|
17
|
-
{ key: 'macos-26-ruby-3.4.8', os: 'macos', arch: 'arm64', ruby: '3.4.8', factor: 1.0 },
|
|
18
|
-
{ key: 'macos-26-intel-ruby-3.4.8', os: 'macos', arch: 'x86_64', ruby: '3.4.8', factor: 0.62 },
|
|
19
|
-
{ key: 'macos-15-intel-ruby-3.4.8', os: 'macos', arch: 'x86_64', ruby: '3.4.8', factor: 0.58 },
|
|
20
|
-
{ key: 'ubuntu-24.04-arm-ruby-3.4.8', os: 'ubuntu-24.04', arch: 'arm64', ruby: '3.4.8', factor: 0.88 },
|
|
21
|
-
{ key: 'ubuntu-24.04-ruby-3.3.12', os: 'ubuntu-24.04', arch: 'x86_64', ruby: '3.3.12', factor: 0.79 },
|
|
22
|
-
{ key: 'ubuntu-22.04-ruby-3.2.11', os: 'ubuntu-22.04', arch: 'x86_64', ruby: '3.2.11', factor: 0.71 },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
// Base rates in iterations/second on the reference env (macos arm64, ruby 3.4).
|
|
26
|
-
// [small, medium, large]
|
|
27
|
-
const base = {
|
|
28
|
-
xml: {
|
|
29
|
-
leptris: { parsing: [8400, 1190, 123], generation: [7400, 746, 12.4], streaming: [12400, 1695, 68] },
|
|
30
|
-
nokogiri: { parsing: [1900, 204, 9.9], generation: [3100, 125, 11.3], streaming: [1500, 20.7, 4.7] },
|
|
31
|
-
ox: { parsing: [2600, 278, 9.9], generation: [5200, 192, 8.0], streaming: [1100, 9.3, 6.3] },
|
|
32
|
-
oga: { parsing: [720, 71, 3.1], generation: [1400, 58, 2.4], streaming: [420, 5.4, 1.9] },
|
|
33
|
-
rexml: { parsing: [410, 39, 1.6], generation: [900, 34, 1.1], streaming: [260, 3.1, 1.2] },
|
|
34
|
-
'libxml-ruby': { parsing: [2300, 255, 12.1], generation: [2900, 118, 10.8], streaming: [null, null, null] },
|
|
35
|
-
},
|
|
36
|
-
json: {
|
|
37
|
-
oj: { parsing: [9600, 1580, 96], generation: [11200, 1350, 88] },
|
|
38
|
-
rapidjson: { parsing: [8900, 1420, 91], generation: [9400, 1180, 79] },
|
|
39
|
-
json: { parsing: [5200, 690, 51], generation: [6100, 705, 47] },
|
|
40
|
-
yajl: { parsing: [6100, 830, 58], generation: [6800, 910, 62] },
|
|
41
|
-
},
|
|
42
|
-
yaml: {
|
|
43
|
-
psych: { parsing: [2100, 230, 15], generation: [2400, 210, 12] },
|
|
44
|
-
},
|
|
45
|
-
toml: {
|
|
46
|
-
tomlib: { parsing: [3600, 430, 27], generation: [4100, 470, 31] },
|
|
47
|
-
'toml-rb': { parsing: [1300, 140, 9], generation: [1700, 160, 11] },
|
|
48
|
-
tomlrb: { parsing: [2400, 300, 19], generation: [null, null, null] },
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// Allocated MB during parse of the large document, reference env.
|
|
53
|
-
const memoryLarge = {
|
|
54
|
-
xml: { leptris: 0.01, nokogiri: 539.9, ox: 237.4, oga: 612.0, rexml: 890.5, 'libxml-ruby': 301.2 },
|
|
55
|
-
json: { oj: 82.0, rapidjson: 96.0, json: 168.0, yajl: 142.0 },
|
|
56
|
-
yaml: { psych: 210.0 },
|
|
57
|
-
toml: { tomlib: 190.0, 'toml-rb': 420.0, tomlrb: 355.0 },
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// libs unavailable per env (glibc / platform gaps) -- mirrors real coverage
|
|
61
|
-
const unavailable = new Set([
|
|
62
|
-
'ubuntu-22.04-ruby-3.2.11|xml|leptris', // ubuntu-22.04 glibc: precompiled gem skips
|
|
63
|
-
'ubuntu-22.04-ruby-3.2.11|xml|libxml-ruby',
|
|
64
|
-
'macos-26-ruby-3.4.8|toml|tomlrb',
|
|
65
|
-
]);
|
|
66
|
-
|
|
67
|
-
const sizes = ['small', 'medium', 'large'];
|
|
68
|
-
const sizeScale = { small: 1.0, medium: 0.16, large: 0.02 };
|
|
69
|
-
const memRetainedRatio = 0.22;
|
|
70
|
-
|
|
71
|
-
const combined = {};
|
|
72
|
-
const environments = {};
|
|
73
|
-
|
|
74
|
-
for (const env of envs) {
|
|
75
|
-
environments[env.key] = {
|
|
76
|
-
ruby_version: env.ruby,
|
|
77
|
-
ruby_platform: `${env.arch}-${env.os.startsWith('macos') ? 'darwin' : 'linux'}`,
|
|
78
|
-
os: env.os,
|
|
79
|
-
arch: env.arch,
|
|
80
|
-
timestamp: '2026-08-23T09:00:00Z',
|
|
81
|
-
source_file: `data/${env.key}.yaml`,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
for (const [format, serializers] of Object.entries(base)) {
|
|
85
|
-
for (const [serializer, ops] of Object.entries(serializers)) {
|
|
86
|
-
if (unavailable.has(`${env.key}|${format}|${serializer}`)) continue;
|
|
87
|
-
|
|
88
|
-
for (const [op, rates] of Object.entries(ops)) {
|
|
89
|
-
rates.forEach((ips, i) => {
|
|
90
|
-
if (ips == null) return;
|
|
91
|
-
const size = sizes[i];
|
|
92
|
-
combined[op] ??= {};
|
|
93
|
-
combined[op][size] ??= {};
|
|
94
|
-
combined[op][size][format] ??= {};
|
|
95
|
-
combined[op][size][format][serializer] ??= {};
|
|
96
|
-
const scaled = ips * env.factor;
|
|
97
|
-
combined[op][size][format][serializer][env.key] = {
|
|
98
|
-
iterations_per_second: Number(scaled.toFixed(scaled < 10 ? 2 : 1)),
|
|
99
|
-
time_per_iteration: Number((1000 / scaled).toFixed(4)),
|
|
100
|
-
};
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const mem = memoryLarge[format]?.[serializer];
|
|
105
|
-
if (mem != null) {
|
|
106
|
-
combined.memory ??= {};
|
|
107
|
-
for (const size of sizes) {
|
|
108
|
-
const factor = size === 'large' ? 1 : size === 'medium' ? 0.14 : 0.012;
|
|
109
|
-
combined.memory[size] ??= {};
|
|
110
|
-
combined.memory[size][format] ??= {};
|
|
111
|
-
combined.memory[size][format][serializer] ??= {};
|
|
112
|
-
const allocated = mem * env.factor * factor;
|
|
113
|
-
combined.memory[size][format][serializer][env.key] = {
|
|
114
|
-
allocated_memory: Number(allocated.toFixed(2)),
|
|
115
|
-
retained_memory: Number((allocated * memRetainedRatio).toFixed(2)),
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const libraries = [
|
|
124
|
-
{ name: 'leptris', format: 'xml', version: '1.6.0', features: { xpath: true, namespaces: true, validation: false, streaming: true, stax: true } },
|
|
125
|
-
{ name: 'nokogiri', format: 'xml', version: '1.19.4', features: { xpath: true, namespaces: true, validation: false, streaming: true, stax: true } },
|
|
126
|
-
{ name: 'ox', format: 'xml', version: '2.14.23', features: { xpath: false, namespaces: true, validation: false, streaming: true, stax: false } },
|
|
127
|
-
{ name: 'oga', format: 'xml', version: '3.4', features: { xpath: true, namespaces: true, validation: false, streaming: true, stax: false } },
|
|
128
|
-
{ name: 'rexml', format: 'xml', version: '3.4.1', features: { xpath: true, namespaces: true, validation: false, streaming: false, stax: false } },
|
|
129
|
-
{ name: 'libxml-ruby', format: 'xml', version: '5.0.6', features: { xpath: true, namespaces: true, validation: false, streaming: true, stax: true } },
|
|
130
|
-
{ name: 'oj', format: 'json', version: '3.16.11', features: { pretty_print: true, streaming: true, symbol_keys: true, custom_types: true } },
|
|
131
|
-
{ name: 'json', format: 'json', version: '2.21.2', features: { pretty_print: true, streaming: false, symbol_keys: false, custom_types: false } },
|
|
132
|
-
{ name: 'rapidjson', format: 'json', version: '0.4.0', features: { pretty_print: true, streaming: false, symbol_keys: false, custom_types: false } },
|
|
133
|
-
{ name: 'yajl', format: 'json', version: '1.4.3', features: { pretty_print: true, streaming: true, symbol_keys: false, custom_types: false } },
|
|
134
|
-
{ name: 'psych', format: 'yaml', version: '4.0.4', features: { streaming: false } },
|
|
135
|
-
{ name: 'toml-rb', format: 'toml', version: '4.2.1', features: { comments: false, arrays_of_tables: true, inline_tables: true, multiline_strings: true } },
|
|
136
|
-
{ name: 'tomlib', format: 'toml', version: '0.7.3', features: { comments: false, arrays_of_tables: true, inline_tables: true, multiline_strings: true } },
|
|
137
|
-
{ name: 'tomlrb', format: 'toml', version: '2.0.4', features: { comments: false, arrays_of_tables: true, inline_tables: true, multiline_strings: true } },
|
|
138
|
-
];
|
|
139
|
-
|
|
140
|
-
const payload = {
|
|
141
|
-
combined_results: combined,
|
|
142
|
-
environments,
|
|
143
|
-
libraries,
|
|
144
|
-
metadata: {
|
|
145
|
-
resultset_name: 'weekly-benchmark',
|
|
146
|
-
resultset_description: 'Sample data for development; CI replaces this with the live resultset',
|
|
147
|
-
total_runs: envs.length,
|
|
148
|
-
generated_at: '2026-08-23T09:00:00Z',
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
mkdirSync(dirname(outPath), { recursive: true });
|
|
153
|
-
writeFileSync(outPath, JSON.stringify(payload, null, 2) + '\n');
|
|
154
|
-
console.log(`wrote ${outPath} (${envs.length} environments)`);
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type { Payload } from '../lib/dashboard';
|
|
3
|
-
import { availability, environmentLabel, environmentList, runnerShortLabel } from '../lib/dashboard';
|
|
4
|
-
import { channelColor } from '../lib/channels';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
payload: Payload;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const { payload } = Astro.props;
|
|
11
|
-
const { serializers, envKeys, cells } = availability(payload);
|
|
12
|
-
const envs = environmentList(payload);
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
<section aria-label="Availability matrix" class="mt-12">
|
|
16
|
-
<div class="mb-3">
|
|
17
|
-
<p class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute">Will it run on my stack — coverage across measured environments</p>
|
|
18
|
-
<p class="font-mono text-[11px] text-inkmute mt-1">Derived from the runs themselves: a dot means the library loaded and completed the benchmark on that platform.</p>
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
<div class="overflow-x-auto border border-line bg-panel">
|
|
22
|
-
<table class="w-full border-collapse">
|
|
23
|
-
<thead>
|
|
24
|
-
<tr>
|
|
25
|
-
<th class="text-left font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute font-normal px-3 py-2 border-b border-line">Library</th>
|
|
26
|
-
{
|
|
27
|
-
envs.map(({ key, env }) => (
|
|
28
|
-
<th class="font-mono text-[10px] uppercase tracking-wider text-inkmute font-normal px-2 py-2 border-b border-line text-center whitespace-nowrap">
|
|
29
|
-
<span class="block">{runnerShortLabel(key, env)}</span>
|
|
30
|
-
<span class="block text-inkmute/70">{env.arch === 'x86_64' ? 'x64' : 'arm64'} · {env.ruby_version}</span>
|
|
31
|
-
</th>
|
|
32
|
-
))
|
|
33
|
-
}
|
|
34
|
-
</tr>
|
|
35
|
-
</thead>
|
|
36
|
-
<tbody>
|
|
37
|
-
{
|
|
38
|
-
serializers.map((serializer) => (
|
|
39
|
-
<tr>
|
|
40
|
-
<td class="px-3 py-1.5 border-b border-line/50 font-mono text-[12px] whitespace-nowrap">
|
|
41
|
-
<a class="flex items-center gap-2 hover:text-phosphor transition-colors" href={`${import.meta.env.BASE_URL}library/${serializer}/`}>
|
|
42
|
-
<span class="h-2 w-2 rounded-full" style={`background: ${channelColor(serializer)}`} />
|
|
43
|
-
{serializer}
|
|
44
|
-
</a>
|
|
45
|
-
</td>
|
|
46
|
-
{envKeys.map((envKey) => (
|
|
47
|
-
<td class="px-2 py-1.5 border-b border-line/50 text-center">
|
|
48
|
-
{cells.get(serializer)?.has(envKey) ? (
|
|
49
|
-
<span class="inline-block h-2 w-2 rounded-full" style={`background: ${channelColor(serializer)}`} title={`measured on ${environmentLabel(envKey, payload.environments[envKey])}`} />
|
|
50
|
-
) : (
|
|
51
|
-
<span class="inline-block h-2 w-2 rounded-full border border-line" title="not measured on this environment" />
|
|
52
|
-
)}
|
|
53
|
-
</td>
|
|
54
|
-
))}
|
|
55
|
-
</tr>
|
|
56
|
-
))
|
|
57
|
-
}
|
|
58
|
-
</tbody>
|
|
59
|
-
</table>
|
|
60
|
-
</div>
|
|
61
|
-
</section>
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, ref } from 'vue';
|
|
3
|
-
import type { LeaderRow } from '../lib/dashboard';
|
|
4
|
-
import { formatIps } from '../lib/dashboard';
|
|
5
|
-
import { channelColor } from '../lib/channels';
|
|
6
|
-
|
|
7
|
-
const props = defineProps<{
|
|
8
|
-
rows: LeaderRow[];
|
|
9
|
-
reference: string | null;
|
|
10
|
-
caption: string;
|
|
11
|
-
}>();
|
|
12
|
-
|
|
13
|
-
const emit = defineEmits<{ pin: [serializer: string] }>();
|
|
14
|
-
|
|
15
|
-
const baseUrl = import.meta.env.BASE_URL;
|
|
16
|
-
|
|
17
|
-
const logScale = ref(false);
|
|
18
|
-
|
|
19
|
-
const effectiveReference = computed(() => props.reference ?? props.rows[0]?.serializer ?? null);
|
|
20
|
-
|
|
21
|
-
const maxIps = computed(() => props.rows[0]?.ips ?? 0);
|
|
22
|
-
const minIps = computed(() => props.rows[props.rows.length - 1]?.ips ?? 0);
|
|
23
|
-
|
|
24
|
-
const logSpan = computed(() => ({
|
|
25
|
-
lo: Math.log10(Math.max(minIps.value * 0.8, 0.01)),
|
|
26
|
-
hi: Math.log10(maxIps.value * 1.05),
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
function widthFor(row: LeaderRow): number {
|
|
30
|
-
if (maxIps.value <= 0) return 0;
|
|
31
|
-
if (!logScale.value) return (row.ips / maxIps.value) * 100;
|
|
32
|
-
const { lo, hi } = logSpan.value;
|
|
33
|
-
return ((Math.log10(row.ips) - lo) / (hi - lo)) * 100;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const ticks = computed(() => {
|
|
37
|
-
const positions = [0, 0.25, 0.5, 0.75, 1];
|
|
38
|
-
return positions.map((p) => {
|
|
39
|
-
const value = logScale.value
|
|
40
|
-
? 10 ** (logSpan.value.lo + p * (logSpan.value.hi - logSpan.value.lo))
|
|
41
|
-
: p * maxIps.value;
|
|
42
|
-
return { pct: p * 100, label: formatIps(value) };
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
</script>
|
|
46
|
-
|
|
47
|
-
<template>
|
|
48
|
-
<div>
|
|
49
|
-
<div class="flex items-baseline justify-between gap-4 mb-3 flex-wrap">
|
|
50
|
-
<div>
|
|
51
|
-
<p class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute">Field ranking — iterations per second, higher is better</p>
|
|
52
|
-
<p class="font-mono text-[11px] text-inkmute mt-1">{{ caption }}</p>
|
|
53
|
-
</div>
|
|
54
|
-
<div class="flex border border-line divide-x divide-line" role="group" aria-label="Scale">
|
|
55
|
-
<button
|
|
56
|
-
type="button"
|
|
57
|
-
class="px-2.5 py-1 font-mono text-[10px] uppercase tracking-wider transition-colors"
|
|
58
|
-
:class="!logScale ? 'bg-panel2 text-ink' : 'text-inkmute hover:text-inkdim'"
|
|
59
|
-
:aria-pressed="!logScale"
|
|
60
|
-
@click="logScale = false"
|
|
61
|
-
>
|
|
62
|
-
Linear
|
|
63
|
-
</button>
|
|
64
|
-
<button
|
|
65
|
-
type="button"
|
|
66
|
-
class="px-2.5 py-1 font-mono text-[10px] uppercase tracking-wider transition-colors"
|
|
67
|
-
:class="logScale ? 'bg-panel2 text-ink' : 'text-inkmute hover:text-inkdim'"
|
|
68
|
-
:aria-pressed="logScale"
|
|
69
|
-
@click="logScale = true"
|
|
70
|
-
>
|
|
71
|
-
Log
|
|
72
|
-
</button>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<!-- axis -->
|
|
77
|
-
<div class="grid grid-cols-[minmax(7rem,10rem)_1fr_minmax(8rem,9rem)] gap-x-3 items-center mb-1">
|
|
78
|
-
<span />
|
|
79
|
-
<div class="relative h-4">
|
|
80
|
-
<span
|
|
81
|
-
v-for="tick in ticks"
|
|
82
|
-
:key="tick.pct"
|
|
83
|
-
class="absolute top-0 font-mono text-[10px] text-inkmute tabular -translate-x-1/2"
|
|
84
|
-
:style="{ left: `${tick.pct}%` }"
|
|
85
|
-
>{{ tick.label }}</span>
|
|
86
|
-
</div>
|
|
87
|
-
<span class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute text-right">vs ref</span>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
<ul class="space-y-1.5" role="list">
|
|
91
|
-
<li v-for="(row, i) in rows" :key="row.serializer">
|
|
92
|
-
<div class="grid grid-cols-[minmax(7rem,10rem)_1fr_minmax(8rem,9rem)] gap-x-3 items-center py-0.5 group">
|
|
93
|
-
<a
|
|
94
|
-
:href="`${baseUrl}library/${row.serializer}/`"
|
|
95
|
-
class="flex items-center gap-2 font-mono text-[13px] truncate min-w-0"
|
|
96
|
-
:style="{ color: channelColor(row.serializer) }"
|
|
97
|
-
:title="`${row.serializer} — library page`"
|
|
98
|
-
>
|
|
99
|
-
<span class="h-2 w-2 shrink-0 rounded-full" :style="{ background: channelColor(row.serializer) }" />
|
|
100
|
-
<span class="truncate group-hover:translate-x-0.5 transition-transform">{{ row.serializer }}</span>
|
|
101
|
-
</a>
|
|
102
|
-
|
|
103
|
-
<button
|
|
104
|
-
type="button"
|
|
105
|
-
class="relative h-7 graticule rounded-sm overflow-hidden w-full text-left"
|
|
106
|
-
:aria-pressed="row.serializer === reference"
|
|
107
|
-
:title="row.serializer === reference ? 'Unpin reference' : 'Pin as reference'"
|
|
108
|
-
:aria-label="`Pin ${row.serializer} as reference`"
|
|
109
|
-
@click="emit('pin', row.serializer)"
|
|
110
|
-
>
|
|
111
|
-
<span
|
|
112
|
-
class="absolute inset-y-[3px] left-0 rounded-[2px] trace-in"
|
|
113
|
-
:style="{
|
|
114
|
-
width: `${Math.max(widthFor(row), 0.75)}%`,
|
|
115
|
-
background: channelColor(row.serializer),
|
|
116
|
-
opacity: row.serializer === effectiveReference || !reference ? 0.95 : 0.6,
|
|
117
|
-
animationDelay: `${i * 60}ms`,
|
|
118
|
-
}"
|
|
119
|
-
/>
|
|
120
|
-
</button>
|
|
121
|
-
|
|
122
|
-
<span class="flex items-baseline justify-end gap-2 whitespace-nowrap">
|
|
123
|
-
<span class="tabular font-mono text-[13px] text-ink">{{ formatIps(row.ips) }}<span class="text-inkmute text-[11px]"> ips</span></span>
|
|
124
|
-
<span
|
|
125
|
-
v-if="row.serializer === effectiveReference"
|
|
126
|
-
class="font-mono text-[10px] uppercase tracking-wider border border-phosphor text-phosphor px-1.5 py-px"
|
|
127
|
-
>ref</span>
|
|
128
|
-
<span v-else-if="row.ratioToRef" class="tabular font-mono text-[11px] text-inkmute">×{{ row.ratioToRef.toFixed(1) }}</span>
|
|
129
|
-
</span>
|
|
130
|
-
</div>
|
|
131
|
-
</li>
|
|
132
|
-
</ul>
|
|
133
|
-
</div>
|
|
134
|
-
</template>
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, ref } from 'vue';
|
|
3
|
-
import type { LeaderRow } from '../lib/dashboard';
|
|
4
|
-
|
|
5
|
-
const props = defineProps<{
|
|
6
|
-
rows: LeaderRow[];
|
|
7
|
-
reference: string | null;
|
|
8
|
-
format: string;
|
|
9
|
-
op: string;
|
|
10
|
-
size: string;
|
|
11
|
-
envLabel: string;
|
|
12
|
-
generatedAt: string;
|
|
13
|
-
}>();
|
|
14
|
-
|
|
15
|
-
const copied = ref(false);
|
|
16
|
-
|
|
17
|
-
const effectiveReference = computed(() => props.reference ?? props.rows[0]?.serializer ?? null);
|
|
18
|
-
const second = computed(() => props.rows.find((r) => r.serializer !== effectiveReference.value));
|
|
19
|
-
const ratio = computed(() => second.value?.ratioToRef ?? null);
|
|
20
|
-
|
|
21
|
-
const opVerb = computed(() => ({ parsing: 'parses', generation: 'generates', streaming: 'streams' })[props.op] ?? props.op);
|
|
22
|
-
|
|
23
|
-
const citation = computed(() => {
|
|
24
|
-
const date = props.generatedAt.slice(0, 10);
|
|
25
|
-
if (!effectiveReference.value || !second.value || !ratio.value) {
|
|
26
|
-
return 'Pin a reference library to generate a citable claim.';
|
|
27
|
-
}
|
|
28
|
-
return `${effectiveReference.value} ${opVerb.value} ${props.size} ${props.format.toUpperCase()} ${ratio.value.toFixed(1)}× faster than ${second.value.serializer} on ${props.envLabel} — serialbench, ${date}`;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
async function copy() {
|
|
32
|
-
await navigator.clipboard.writeText(citation.value);
|
|
33
|
-
copied.value = true;
|
|
34
|
-
setTimeout(() => (copied.value = false), 1600);
|
|
35
|
-
}
|
|
36
|
-
</script>
|
|
37
|
-
|
|
38
|
-
<template>
|
|
39
|
-
<figure class="border border-line bg-panel px-4 py-3 flex flex-wrap items-center gap-4">
|
|
40
|
-
<span class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute shrink-0">Cite this</span>
|
|
41
|
-
<blockquote class="flex-1 min-w-64 font-mono text-[12px] leading-relaxed text-inkdim">“{{ citation }}”</blockquote>
|
|
42
|
-
<button
|
|
43
|
-
type="button"
|
|
44
|
-
class="shrink-0 font-mono text-[10px] uppercase tracking-wider border border-line px-2.5 py-1 text-inkdim hover:border-phosphor hover:text-phosphor transition-colors"
|
|
45
|
-
@click="copy"
|
|
46
|
-
>
|
|
47
|
-
{{ copied ? 'Copied' : 'Copy' }}
|
|
48
|
-
</button>
|
|
49
|
-
</figure>
|
|
50
|
-
</template>
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, onMounted, ref, watch } from 'vue';
|
|
3
|
-
import type { Payload } from '../lib/dashboard';
|
|
4
|
-
import {
|
|
5
|
-
availableFormats,
|
|
6
|
-
availableOperations,
|
|
7
|
-
availableSizes,
|
|
8
|
-
environmentLabel,
|
|
9
|
-
environmentList,
|
|
10
|
-
leaderboard,
|
|
11
|
-
memoryRows,
|
|
12
|
-
OPERATIONS,
|
|
13
|
-
OPERATION_LABELS,
|
|
14
|
-
serializersFor,
|
|
15
|
-
} from '../lib/dashboard';
|
|
16
|
-
import CalibratedLeaderboard from './CalibratedLeaderboard.vue';
|
|
17
|
-
import OpsChart from './OpsChart.vue';
|
|
18
|
-
import MemoryPanel from './MemoryPanel.vue';
|
|
19
|
-
import CitationBox from './CitationBox.vue';
|
|
20
|
-
|
|
21
|
-
const props = defineProps<{ payload: Payload }>();
|
|
22
|
-
|
|
23
|
-
const formats = computed(() => availableFormats(props.payload));
|
|
24
|
-
const format = ref(formats.value[0] ?? 'xml');
|
|
25
|
-
|
|
26
|
-
const operations = computed(() => availableOperations(props.payload, format.value));
|
|
27
|
-
const op = ref<string>('parsing');
|
|
28
|
-
|
|
29
|
-
const sizes = computed(() => availableSizes(props.payload, op.value, format.value));
|
|
30
|
-
const size = ref<string>('large');
|
|
31
|
-
|
|
32
|
-
const envs = computed(() => environmentList(props.payload));
|
|
33
|
-
const envKey = ref(envs.value[0]?.key ?? '');
|
|
34
|
-
|
|
35
|
-
const reference = ref<string | null>(null);
|
|
36
|
-
|
|
37
|
-
function syncToAvailable() {
|
|
38
|
-
if (!operations.value.includes(op.value as (typeof OPERATIONS)[number])) {
|
|
39
|
-
op.value = operations.value[0] ?? 'parsing';
|
|
40
|
-
}
|
|
41
|
-
if (!sizes.value.includes(size.value)) {
|
|
42
|
-
size.value = sizes.value[sizes.value.length - 1] ?? 'small';
|
|
43
|
-
}
|
|
44
|
-
if (!envs.value.some((e) => e.key === envKey.value)) {
|
|
45
|
-
envKey.value = envs.value[0]?.key ?? '';
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const state = computed(() => ({ format: format.value, op: op.value, size: size.value, env: envKey.value, ref: reference.value }));
|
|
50
|
-
|
|
51
|
-
function syncUrl() {
|
|
52
|
-
const params = new URLSearchParams();
|
|
53
|
-
params.set('f', format.value);
|
|
54
|
-
params.set('op', op.value);
|
|
55
|
-
if (sizes.value.length > 1) params.set('sz', size.value);
|
|
56
|
-
params.set('env', envKey.value);
|
|
57
|
-
if (reference.value) params.set('ref', reference.value);
|
|
58
|
-
history.replaceState(null, '', `?${params.toString()}`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
watch(state, syncUrl);
|
|
62
|
-
watch([format], () => {
|
|
63
|
-
reference.value = null;
|
|
64
|
-
syncToAvailable();
|
|
65
|
-
});
|
|
66
|
-
watch([op], syncToAvailable);
|
|
67
|
-
|
|
68
|
-
onMounted(() => {
|
|
69
|
-
const params = new URLSearchParams(location.search);
|
|
70
|
-
const f = params.get('f');
|
|
71
|
-
if (f && formats.value.includes(f)) format.value = f;
|
|
72
|
-
syncToAvailable();
|
|
73
|
-
const opParam = params.get('op');
|
|
74
|
-
if (opParam && operations.value.includes(opParam as (typeof OPERATIONS)[number])) op.value = opParam;
|
|
75
|
-
const sz = params.get('sz');
|
|
76
|
-
if (sz && sizes.value.includes(sz)) size.value = sz;
|
|
77
|
-
const env = params.get('env');
|
|
78
|
-
if (env && envs.value.some((e) => e.key === env)) envKey.value = env;
|
|
79
|
-
reference.value = params.get('ref');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const rows = computed(() =>
|
|
83
|
-
leaderboard(props.payload, op.value, size.value, format.value, envKey.value, reference.value),
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const mem = computed(() => memoryRows(props.payload, size.value, format.value, envKey.value));
|
|
87
|
-
|
|
88
|
-
const serializers = computed(() => serializersFor(props.payload, format.value));
|
|
89
|
-
|
|
90
|
-
const envLabel = computed(() => {
|
|
91
|
-
const entry = envs.value.find((e) => e.key === envKey.value);
|
|
92
|
-
return entry ? environmentLabel(entry.key, entry.env) : '';
|
|
93
|
-
});
|
|
94
|
-
</script>
|
|
95
|
-
|
|
96
|
-
<template>
|
|
97
|
-
<section aria-label="Benchmark console">
|
|
98
|
-
<!-- control rail -->
|
|
99
|
-
<div class="flex flex-wrap items-end gap-x-8 gap-y-4 border-y border-line py-3 mb-8">
|
|
100
|
-
<fieldset>
|
|
101
|
-
<legend class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute mb-1.5">Format</legend>
|
|
102
|
-
<div class="flex border border-line divide-x divide-line" role="group">
|
|
103
|
-
<button
|
|
104
|
-
v-for="f in formats"
|
|
105
|
-
:key="f"
|
|
106
|
-
type="button"
|
|
107
|
-
class="px-3 py-1.5 font-mono text-xs uppercase tracking-wide transition-colors"
|
|
108
|
-
:class="f === format ? 'bg-phosphor text-phosphorink' : 'text-inkdim hover:text-ink'"
|
|
109
|
-
:aria-pressed="f === format"
|
|
110
|
-
@click="format = f"
|
|
111
|
-
>
|
|
112
|
-
{{ f }}
|
|
113
|
-
</button>
|
|
114
|
-
</div>
|
|
115
|
-
</fieldset>
|
|
116
|
-
|
|
117
|
-
<fieldset>
|
|
118
|
-
<legend class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute mb-1.5">Operation</legend>
|
|
119
|
-
<div class="flex border border-line divide-x divide-line" role="group">
|
|
120
|
-
<button
|
|
121
|
-
v-for="o in operations"
|
|
122
|
-
:key="o"
|
|
123
|
-
type="button"
|
|
124
|
-
class="px-3 py-1.5 font-mono text-xs uppercase tracking-wide transition-colors"
|
|
125
|
-
:class="o === op ? 'bg-phosphor text-phosphorink' : 'text-inkdim hover:text-ink'"
|
|
126
|
-
:aria-pressed="o === op"
|
|
127
|
-
@click="op = o"
|
|
128
|
-
>
|
|
129
|
-
{{ OPERATION_LABELS[o as keyof typeof OPERATION_LABELS] ?? o }}
|
|
130
|
-
</button>
|
|
131
|
-
</div>
|
|
132
|
-
</fieldset>
|
|
133
|
-
|
|
134
|
-
<fieldset v-if="sizes.length > 1">
|
|
135
|
-
<legend class="font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute mb-1.5">Input size</legend>
|
|
136
|
-
<div class="flex border border-line divide-x divide-line" role="group">
|
|
137
|
-
<button
|
|
138
|
-
v-for="s in sizes"
|
|
139
|
-
:key="s"
|
|
140
|
-
type="button"
|
|
141
|
-
class="px-3 py-1.5 font-mono text-xs uppercase tracking-wide transition-colors"
|
|
142
|
-
:class="s === size ? 'bg-phosphor text-phosphorink' : 'text-inkdim hover:text-ink'"
|
|
143
|
-
:aria-pressed="s === size"
|
|
144
|
-
@click="size = s"
|
|
145
|
-
>
|
|
146
|
-
{{ s }}
|
|
147
|
-
</button>
|
|
148
|
-
</div>
|
|
149
|
-
</fieldset>
|
|
150
|
-
|
|
151
|
-
<label class="ml-auto">
|
|
152
|
-
<span class="block font-mono text-[10px] uppercase tracking-[0.14em] text-inkmute mb-1.5">Environment</span>
|
|
153
|
-
<select
|
|
154
|
-
v-model="envKey"
|
|
155
|
-
class="bg-panel border border-line px-3 py-1.5 font-mono text-xs text-inkdim hover:text-ink focus:text-ink transition-colors max-w-64"
|
|
156
|
-
>
|
|
157
|
-
<option v-for="e in envs" :key="e.key" :value="e.key">{{ environmentLabel(e.key, e.env) }}</option>
|
|
158
|
-
</select>
|
|
159
|
-
</label>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<CalibratedLeaderboard
|
|
163
|
-
:rows="rows"
|
|
164
|
-
:reference="reference"
|
|
165
|
-
:caption="`${envLabel} · ${OPERATION_LABELS[op as keyof typeof OPERATION_LABELS] ?? op} · ${size} input`"
|
|
166
|
-
@pin="(name: string) => (reference = reference === name ? null : name)"
|
|
167
|
-
/>
|
|
168
|
-
|
|
169
|
-
<div class="grid lg:grid-cols-2 gap-6 mt-10">
|
|
170
|
-
<OpsChart
|
|
171
|
-
v-if="sizes.length > 1"
|
|
172
|
-
:payload="payload"
|
|
173
|
-
:op="op"
|
|
174
|
-
:format="format"
|
|
175
|
-
:sizes="sizes"
|
|
176
|
-
:env-key="envKey"
|
|
177
|
-
:serializers="serializers"
|
|
178
|
-
/>
|
|
179
|
-
<MemoryPanel v-if="mem.length > 0" :rows="mem" :size="size" />
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
<CitationBox
|
|
183
|
-
class="mt-6"
|
|
184
|
-
:rows="rows"
|
|
185
|
-
:reference="reference"
|
|
186
|
-
:format="format"
|
|
187
|
-
:op="op"
|
|
188
|
-
:size="size"
|
|
189
|
-
:env-label="envLabel"
|
|
190
|
-
:generated-at="payload.metadata.generated_at"
|
|
191
|
-
/>
|
|
192
|
-
</section>
|
|
193
|
-
</template>
|