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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.github/scripts/push-to-data.sh +65 -0
  3. data/.github/workflows/benchmark.yml +98 -172
  4. data/.github/workflows/release.yml +68 -13
  5. data/Gemfile +1 -1
  6. data/config/benchmarks/full-json.yml +25 -0
  7. data/config/benchmarks/full-toml.yml +25 -0
  8. data/config/benchmarks/full-xml.yml +26 -0
  9. data/config/benchmarks/full-yaml.yml +25 -0
  10. data/config/benchmarks/full.yml +9 -4
  11. data/lib/serialbench/benchmark_runner.rb +32 -320
  12. data/lib/serialbench/cli/benchmark_cli.rb +1 -1
  13. data/lib/serialbench/cli.rb +0 -12
  14. data/lib/serialbench/models/benchmark_result.rb +2 -0
  15. data/lib/serialbench/models/result_store.rb +0 -38
  16. data/lib/serialbench/models.rb +0 -5
  17. data/lib/serialbench/serializers/base_serializer.rb +11 -3
  18. data/lib/serialbench/serializers/json/base_json_serializer.rb +8 -20
  19. data/lib/serialbench/serializers/json/json_serializer.rb +0 -16
  20. data/lib/serialbench/serializers/json/oj_serializer.rb +2 -17
  21. data/lib/serialbench/serializers/json/yajl_serializer.rb +2 -2
  22. data/lib/serialbench/serializers/toml/base_toml_serializer.rb +8 -29
  23. data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +0 -16
  24. data/lib/serialbench/serializers/toml/tomlib_serializer.rb +0 -4
  25. data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +2 -18
  26. data/lib/serialbench/serializers/xml/base_xml_serializer.rb +10 -26
  27. data/lib/serialbench/serializers/xml/leptris_serializer.rb +13 -10
  28. data/lib/serialbench/serializers/xml/libxml_serializer.rb +4 -9
  29. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +4 -9
  30. data/lib/serialbench/serializers/xml/oga_serializer.rb +6 -4
  31. data/lib/serialbench/serializers/xml/ox_serializer.rb +2 -2
  32. data/lib/serialbench/serializers/xml/rexml_serializer.rb +5 -12
  33. data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +1 -9
  34. data/lib/serialbench/serializers/yaml/syck_serializer.rb +0 -8
  35. data/lib/serialbench/test_data.rb +315 -0
  36. data/lib/serialbench/version.rb +1 -1
  37. data/lib/serialbench.rb +2 -1
  38. data/serialbench.gemspec +1 -2
  39. metadata +13 -59
  40. data/lib/serialbench/cli/resultset_cli.rb +0 -301
  41. data/lib/serialbench/models/result_set.rb +0 -79
  42. data/lib/serialbench/site_generator.rb +0 -337
  43. data/lib/serialbench/templates/assets/css/benchmark_report.css +0 -535
  44. data/lib/serialbench/templates/assets/css/format_based.css +0 -474
  45. data/lib/serialbench/templates/assets/css/themes.css +0 -589
  46. data/lib/serialbench/templates/assets/js/chart_helpers.js +0 -411
  47. data/lib/serialbench/templates/assets/js/dashboard.js +0 -795
  48. data/lib/serialbench/templates/assets/js/navigation.js +0 -142
  49. data/lib/serialbench/templates/base.liquid +0 -49
  50. data/lib/serialbench/templates/format_based.liquid +0 -507
  51. data/lib/serialbench/templates/partials/chart_section.liquid +0 -4
  52. data/site/.gitignore +0 -5
  53. data/site/README.md +0 -32
  54. data/site/astro.config.mjs +0 -12
  55. data/site/package-lock.json +0 -6160
  56. data/site/package.json +0 -22
  57. data/site/public/favicon.svg +0 -4
  58. data/site/scripts/gen-sample-data.mjs +0 -154
  59. data/site/src/components/AvailabilityMatrix.astro +0 -61
  60. data/site/src/components/CalibratedLeaderboard.vue +0 -134
  61. data/site/src/components/CitationBox.vue +0 -50
  62. data/site/src/components/DashboardConsole.vue +0 -193
  63. data/site/src/components/FeaturesTable.astro +0 -97
  64. data/site/src/components/MemoryPanel.vue +0 -44
  65. data/site/src/components/OpsChart.vue +0 -135
  66. data/site/src/config.ts +0 -18
  67. data/site/src/layouts/Layout.astro +0 -85
  68. data/site/src/lib/channels.ts +0 -24
  69. data/site/src/lib/dashboard.ts +0 -269
  70. data/site/src/pages/index.astro +0 -44
  71. data/site/src/pages/library/[slug].astro +0 -128
  72. data/site/src/pages/methodology.astro +0 -55
  73. data/site/src/styles/global.css +0 -103
  74. 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
- }
@@ -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>