diff --git a/.gitignore b/.gitignore index da14473..c00b7f3 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ backend/frontend/dist/ /bin/* !/bin/collect-proof-stats.mjs +!/bin/collect-performance-stats.mjs _data/ # Local environment files diff --git a/Makefile b/Makefile index 0ea098c..33755f4 100644 --- a/Makefile +++ b/Makefile @@ -78,10 +78,16 @@ docs-proof-stats: ##@docs Refresh checked-in proof statistics from sibling repos docs-check-proof-stats: ##@docs Verify checked-in proof statistics match sibling repositories @cd docs && npm run proof:check +docs-performance-stats: ##@docs Refresh checked-in performance statistics from sibling repositories + @cd docs && npm run performance:refresh + +docs-check-performance-stats: ##@docs Verify checked-in performance statistics match sibling repositories + @cd docs && npm run performance:check + docs-check-scenarios: ##@docs Verify generated scenario pages match framework specs @cd ../goforj && go run ./cmd/forj scenario:generate --all --check -docs-build: docs-check-proof-stats docs-check-scenarios ##@docs Verify generated evidence and build VitePress docs +docs-build: docs-check-proof-stats docs-check-performance-stats docs-check-scenarios ##@docs Verify generated evidence and build VitePress docs @cd docs && npm run build docs-embed: ##@docs Copy built docs into backend embed folder diff --git a/bin/collect-performance-stats.mjs b/bin/collect-performance-stats.mjs new file mode 100644 index 0000000..3d5e478 --- /dev/null +++ b/bin/collect-performance-stats.mjs @@ -0,0 +1,175 @@ +#!/usr/bin/env node + +import crypto from 'node:crypto' +import fs from 'node:fs' +import path from 'node:path' +import { execFileSync } from 'node:child_process' +import { fileURLToPath } from 'node:url' + +const docsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const check = process.argv.includes('--check') +const rootArg = process.argv.slice(2).find((arg) => arg !== '--check') +const reposRoot = path.resolve(rootArg || path.join(docsRoot, '..')) +const outputFile = path.join(docsRoot, 'docs', '.vitepress', 'data', 'performance-stats.json') +const proofFile = path.join(docsRoot, 'docs', '.vitepress', 'data', 'proof-stats.json') + +const benchmarkFile = (repo) => path.join(reposRoot, repo, 'docs', 'bench', 'benchmarks_rows.json') +const readJSON = (file) => JSON.parse(fs.readFileSync(file, 'utf8')) +const required = (value, description) => { + if (value === undefined || value === null) throw new Error(`missing benchmark row: ${description}`) + return value +} +const median = (values) => { + const sorted = [...values].sort((a, b) => a - b) + return sorted[Math.floor(sorted.length / 2)] +} +const labels = { + ftp: 'FTP', + gcppubsub: 'Google Pub/Sub', + gcs: 'GCS', + mysql: 'MySQL', + nats: 'NATS', + nats_bucket_ttl: 'NATS KV + TTL', + natsjetstream: 'NATS JetStream', + postgres: 'PostgreSQL', + rabbitmq: 'RabbitMQ', + rclone_local: 'rclone local', + sftp: 'SFTP', + sns: 'SNS', + sqs: 'SQS', + sqlite: 'SQLite', + workerpool: 'Worker pool' +} +const title = (value) => { + const normalized = value.replace(/^sql_/, '') + return labels[normalized] || normalized + .replaceAll('_', ' ') + .replace(/\b\w/g, (character) => character.toUpperCase()) +} + +// source records the exact committed benchmark artifact consumed by the page. +const source = (repo) => { + const file = benchmarkFile(repo) + const relative = path.relative(path.join(reposRoot, repo), file) + return { + repo, + path: relative, + revision: execFileSync('git', ['-C', path.join(reposRoot, repo), 'log', '-1', '--format=%H', '--', relative], { encoding: 'utf8' }).trim(), + sha256: crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') + } +} + +const cacheRaw = readJSON(benchmarkFile('cache')) +const queueRaw = readJSON(benchmarkFile('queue')) +const eventsRaw = readJSON(benchmarkFile('events')) +const storageRaw = readJSON(benchmarkFile('storage')) +const webRaw = readJSON(benchmarkFile('web')) +const proof = readJSON(proofFile) + +const cacheRows = required(cacheRaw.get_bytes, 'cache get_bytes').map((row) => ({ + label: title(row.Driver), + value: row.NsOp, + bytes: row.BytesOp, + allocs: row.AllocsOp, + kind: row.Driver === 'memory' ? 'process' : ['file', 'sql_sqlite'].includes(row.Driver) ? 'local' : 'service' +})) + +const queueRows = queueRaw + .filter((row) => row.driver !== 'null') + .map((row) => ({ + label: title(row.driver), + value: row.ns_op, + bytes: row.b_op, + allocs: row.allocs_op, + kind: ['sync', 'workerpool'].includes(row.driver) ? 'process' : row.driver === 'sqlite' ? 'local' : 'service' + })) + +const eventRows = eventsRaw + .filter((row) => row.name === 'SyncPublishRoundTrip' || row.set === 'Integration') + .map((row) => { + const driver = row.name.includes('/') ? row.name.split('/').at(-1) : 'sync' + return { + label: driver === 'sync' ? 'Synchronous' : title(driver), + value: row.ns_op, + bytes: row.b_op, + allocs: row.allocs_op, + kind: driver === 'sync' ? 'process' : 'service' + } + }) + +const storageRows = required(storageRaw.get_small, 'storage get_small').map((row) => ({ + label: title(row.driver), + value: row.ns_op, + bytes: row.bytes_op, + allocs: row.allocs_op, + kind: row.driver === 'memory' ? 'process' : ['local', 'rclone_local'].includes(row.driver) ? 'local' : 'service' +})) + +const webScenarios = ['live_plain_text', 'static_text', 'path_param_json', 'middleware_chain'].map((scenario) => { + const samples = webRaw.samples.filter((sample) => sample.scenario === scenario) + const frameworks = [...new Set(samples.map((sample) => sample.framework))] + return { + id: scenario, + rows: frameworks.map((framework) => { + const rows = samples.filter((sample) => sample.framework === framework) + return { + label: { + goforj_web: 'GoForj Web', + net_http: 'net/http', + gorilla_mux: 'Gorilla Mux', + httprouter: 'httprouter' + }[framework] || title(framework), + framework, + throughput: median(rows.map((row) => row.throughput_per_second)), + nsOp: median(rows.map((row) => row.nanoseconds_per_op)), + bytes: median(rows.map((row) => row.bytes_per_op)), + allocs: median(rows.map((row) => row.allocs_per_op)) + } + }) + } +}) + +const findRow = (rows, label) => required(rows.find((row) => row.label === label), label) + +const stats = { + generatedAt: new Date().toISOString().slice(0, 10), + totals: proof.totals, + highlights: [ + { library: 'Cache', label: 'Memory GetBytes', value: findRow(cacheRows, 'Memory').value, unit: 'ns/op' }, + { library: 'Queue', label: 'Synchronous Dispatch return', value: findRow(queueRows, 'Sync').value, unit: 'ns/op' }, + { library: 'Events', label: 'Publish + handler round trip', value: findRow(eventRows, 'Synchronous').value, unit: 'ns/op' }, + { library: 'Storage', label: 'Memory small-object Get', value: findRow(storageRows, 'Memory').value, unit: 'ns/op' } + ], + driverStories: [ + { id: 'cache', title: 'Cache reads', operation: 'GetBytes', rows: cacheRows }, + { id: 'queue', title: 'Queue dispatch', operation: 'Queue', rows: queueRows }, + { id: 'events', title: 'Event round trips', operation: 'Publish + handle', rows: eventRows }, + { id: 'storage', title: 'Small-file reads', operation: 'Get', rows: storageRows } + ], + web: { + metadata: webRaw.metadata, + scenarios: webScenarios + }, + benchmarkLibraries: proof.repos + .filter((repo) => repo.benchmarks > 0) + .map((repo) => ({ repo: repo.repo, benchmarks: repo.benchmarks })), + sources: ['cache', 'events', 'queue', 'storage', 'web'].map(source) +} + +const output = JSON.stringify(stats, null, 2) + '\n' + +if (check) { + const current = readJSON(outputFile) + const expected = JSON.parse(output) + delete current.generatedAt + delete expected.generatedAt + if (JSON.stringify(current) !== JSON.stringify(expected)) { + throw new Error(`performance statistics are stale; run: node bin/collect-performance-stats.mjs ${reposRoot}`) + } + console.log(`performance statistics are current: ${outputFile}`) +} else { + fs.mkdirSync(path.dirname(outputFile), { recursive: true }) + fs.writeFileSync(outputFile, output) + console.log(`wrote ${outputFile}`) + console.log({ highlights: stats.highlights, driverStories: stats.driverStories.length }) +} diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index d5bffa9..d32bc14 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -418,6 +418,7 @@ const llmsSectionOrder = [ 'security/', 'frontend/', 'starter-kits.md', + 'performance.md', 'testing/', 'scenarios/', 'operations/', @@ -440,6 +441,7 @@ const llmsSectionTitles: Record = { 'security/': 'Security', 'frontend/': 'Frontend', 'starter-kits.md': 'Starter Kits', + 'performance.md': 'Performance', 'testing/': 'Testing', 'scenarios/': 'Verified Scenarios', 'operations/': 'Operations', @@ -989,7 +991,7 @@ const operationsSidebar = sectionSidebar('Operations', [ { text: 'Metrics', link: '/operations/metrics' }, { text: 'Inspects', link: '/operations/inspects' }, { text: 'Lighthouse', link: '/operations/lighthouse' }, - { text: 'Performance Benchmarks', link: '/operations/performance-benchmarks' }, + { text: 'Performance', link: '/performance' }, { text: 'Backup and Restore', link: '/operations/backups' } ]) @@ -1388,6 +1390,7 @@ export default defineConfig({ ] }, { text: 'Starter Kits', link: '/starter-kits' }, + { text: 'Performance', link: '/performance' }, { text: 'Guides', items: [ diff --git a/docs/.vitepress/data/content-quality.json b/docs/.vitepress/data/content-quality.json index 7b978d0..ab703a7 100644 --- a/docs/.vitepress/data/content-quality.json +++ b/docs/.vitepress/data/content-quality.json @@ -5,6 +5,7 @@ ["index.md", "landing"], ["**/index.md", "landing"], ["starter-kits.md", "landing"], + ["performance.md", "landing"], ["site-preview.md", "landing"], ["libraries/**/*.md", "generated-library"], ["scenarios/**/*.md", "generated-scenario"], diff --git a/docs/.vitepress/data/performance-stats.json b/docs/.vitepress/data/performance-stats.json new file mode 100644 index 0000000..5ed07fe --- /dev/null +++ b/docs/.vitepress/data/performance-stats.json @@ -0,0 +1,696 @@ +{ + "generatedAt": "2026-08-07", + "totals": { + "unitTests": 1461, + "integrationTests": 952, + "testFunctions": 3401, + "benchmarks": 85, + "drivers": 48, + "libraries": 18 + }, + "highlights": [ + { + "library": "Cache", + "label": "Memory GetBytes", + "value": 70, + "unit": "ns/op" + }, + { + "library": "Queue", + "label": "Synchronous Dispatch return", + "value": 282.5, + "unit": "ns/op" + }, + { + "library": "Events", + "label": "Publish + handler round trip", + "value": 371.8, + "unit": "ns/op" + }, + { + "library": "Storage", + "label": "Memory small-object Get", + "value": 752.9147821215927, + "unit": "ns/op" + } + ], + "driverStories": [ + { + "id": "cache", + "title": "Cache reads", + "operation": "GetBytes", + "rows": [ + { + "label": "Memory", + "value": 70, + "bytes": 1, + "allocs": 1, + "kind": "process" + }, + { + "label": "File", + "value": 100977, + "bytes": 1352, + "allocs": 9, + "kind": "local" + }, + { + "label": "Redis", + "value": 73175, + "bytes": 200, + "allocs": 8, + "kind": "service" + }, + { + "label": "Memcached", + "value": 70363, + "bytes": 192, + "allocs": 9, + "kind": "service" + }, + { + "label": "PostgreSQL", + "value": 86657, + "bytes": 1064, + "allocs": 32, + "kind": "service" + }, + { + "label": "MySQL", + "value": 81964, + "bytes": 744, + "allocs": 26, + "kind": "service" + }, + { + "label": "SQLite", + "value": 7494, + "bytes": 1432, + "allocs": 39, + "kind": "local" + }, + { + "label": "NATS", + "value": 96450, + "bytes": 2573, + "allocs": 40, + "kind": "service" + }, + { + "label": "NATS KV + TTL", + "value": 82872, + "bytes": 2556, + "allocs": 40, + "kind": "service" + } + ] + }, + { + "id": "queue", + "title": "Queue dispatch", + "operation": "Queue", + "rows": [ + { + "label": "Sync", + "value": 282.5, + "bytes": 408, + "allocs": 6, + "kind": "process" + }, + { + "label": "Worker pool", + "value": 650, + "bytes": 456, + "allocs": 7, + "kind": "process" + }, + { + "label": "Redis", + "value": 95295, + "bytes": 2113, + "allocs": 33, + "kind": "service" + }, + { + "label": "NATS", + "value": 774.1, + "bytes": 1258, + "allocs": 13, + "kind": "service" + }, + { + "label": "SQS", + "value": 1873911, + "bytes": 94784, + "allocs": 1082, + "kind": "service" + }, + { + "label": "RabbitMQ", + "value": 165780, + "bytes": 1882, + "allocs": 57, + "kind": "service" + }, + { + "label": "MySQL", + "value": 2286406, + "bytes": 3303, + "allocs": 62, + "kind": "service" + }, + { + "label": "PostgreSQL", + "value": 1056731, + "bytes": 3809, + "allocs": 78, + "kind": "service" + }, + { + "label": "SQLite", + "value": 202380, + "bytes": 1931, + "allocs": 47, + "kind": "local" + } + ] + }, + { + "id": "events", + "title": "Event round trips", + "operation": "Publish + handle", + "rows": [ + { + "label": "Synchronous", + "value": 371.8, + "bytes": 320, + "allocs": 9, + "kind": "process" + }, + { + "label": "Google Pub/Sub", + "value": 52848425, + "bytes": 21971, + "allocs": 369, + "kind": "service" + }, + { + "label": "Kafka", + "value": 499091, + "bytes": 5040, + "allocs": 67, + "kind": "service" + }, + { + "label": "NATS JetStream", + "value": 127165, + "bytes": 3609, + "allocs": 60, + "kind": "service" + }, + { + "label": "NATS", + "value": 73316, + "bytes": 681, + "allocs": 18, + "kind": "service" + }, + { + "label": "Redis", + "value": 95149, + "bytes": 914, + "allocs": 31, + "kind": "service" + }, + { + "label": "SNS", + "value": 2553117, + "bytes": 104563, + "allocs": 1271, + "kind": "service" + } + ] + }, + { + "id": "storage", + "title": "Small-file reads", + "operation": "Get", + "rows": [ + { + "label": "Local", + "value": 13811.52516435556, + "bytes": 1536.2368929893375, + "allocs": 14.001602121429755, + "kind": "local" + }, + { + "label": "Memory", + "value": 752.9147821215927, + "bytes": 496.081603888653, + "allocs": 9.00039452721485, + "kind": "process" + }, + { + "label": "GCS", + "value": 210780.62913907284, + "bytes": 201477.99157134257, + "allocs": 6831.402167369055, + "kind": "service" + }, + { + "label": "FTP", + "value": 243278.00972222222, + "bytes": 5429.788888888889, + "allocs": 99.03194444444445, + "kind": "service" + }, + { + "label": "rclone local", + "value": 17957.60648504438, + "bytes": 2168.0258580883483, + "allocs": 21.000564363039352, + "kind": "local" + }, + { + "label": "Redis", + "value": 79510.93253066788, + "bytes": 936.0327124034529, + "allocs": 24.000227169468424, + "kind": "service" + }, + { + "label": "S3", + "value": 382576.1599081867, + "bytes": 45419.268553940325, + "allocs": 630.1384850803366, + "kind": "service" + }, + { + "label": "SFTP", + "value": 429232.65321888414, + "bytes": 2523.7836909871244, + "allocs": 47.01030042918455, + "kind": "service" + } + ] + } + ], + "web": { + "metadata": { + "go_version": "go1.26.1", + "goos": "linux", + "goarch": "arm64", + "cpu": "arm64 (CPU model unavailable)", + "kernel": "Linux 7.0.11-orbstack-00360-gc9bc4d96ac70", + "gomaxprocs": 1, + "sample_count": 7, + "benchmark_time": "1s", + "repository_revision": "df4a3d1f75f6", + "repository_dirty": false, + "benchmark_input_fingerprint": "sha256:34d8f1b557a3681b44748966456fee27d79c89111d49b01fa6da04ae6b6323c2", + "build_settings": [ + { + "name": "CGO_ENABLED", + "value": "1" + }, + { + "name": "GOARM64", + "value": "v8.0" + }, + { + "name": "GODEBUG", + "value": "" + }, + { + "name": "GOEXPERIMENT", + "value": "" + }, + { + "name": "GOFLAGS", + "value": "" + } + ], + "dependencies": [ + { + "name": "net/http", + "module": "standard library", + "version": "go1.26.1" + }, + { + "name": "GoForj Web", + "module": "github.com/goforj/web", + "version": "local checkout" + }, + { + "name": "Echo", + "module": "github.com/labstack/echo/v5", + "version": "v5.1.0" + }, + { + "name": "Gin", + "module": "github.com/gin-gonic/gin", + "version": "v1.12.0" + }, + { + "name": "Chi", + "module": "github.com/go-chi/chi/v5", + "version": "v5.3.1" + }, + { + "name": "Gorilla Mux", + "module": "github.com/gorilla/mux", + "version": "v1.8.1" + }, + { + "name": "httprouter", + "module": "github.com/julienschmidt/httprouter", + "version": "v1.3.0" + } + ] + }, + "scenarios": [ + { + "id": "live_plain_text", + "rows": [ + { + "label": "GoForj Web", + "framework": "goforj_web", + "throughput": 118993, + "nsOp": 8404, + "bytes": 4891, + "allocs": 60 + }, + { + "label": "net/http", + "framework": "net_http", + "throughput": 117923, + "nsOp": 8480, + "bytes": 4891, + "allocs": 60 + }, + { + "label": "Echo", + "framework": "echo", + "throughput": 117586, + "nsOp": 8504, + "bytes": 4915, + "allocs": 61 + }, + { + "label": "Gin", + "framework": "gin", + "throughput": 118609, + "nsOp": 8431, + "bytes": 4923, + "allocs": 60 + }, + { + "label": "Chi", + "framework": "chi", + "throughput": 116782, + "nsOp": 8563, + "bytes": 5259, + "allocs": 62 + }, + { + "label": "Gorilla Mux", + "framework": "gorilla_mux", + "throughput": 113899, + "nsOp": 8780, + "bytes": 5739, + "allocs": 67 + }, + { + "label": "httprouter", + "framework": "httprouter", + "throughput": 120078, + "nsOp": 8328, + "bytes": 4891, + "allocs": 60 + } + ] + }, + { + "id": "static_text", + "rows": [ + { + "label": "GoForj Web", + "framework": "goforj_web", + "throughput": 11047264, + "nsOp": 90.52, + "bytes": 16, + "allocs": 1 + }, + { + "label": "net/http", + "framework": "net_http", + "throughput": 8105840, + "nsOp": 123.4, + "bytes": 16, + "allocs": 1 + }, + { + "label": "Echo", + "framework": "echo", + "throughput": 7490212, + "nsOp": 133.5, + "bytes": 40, + "allocs": 2 + }, + { + "label": "Gin", + "framework": "gin", + "throughput": 9447802, + "nsOp": 105.8, + "bytes": 48, + "allocs": 1 + }, + { + "label": "Chi", + "framework": "chi", + "throughput": 4079741, + "nsOp": 245.1, + "bytes": 384, + "allocs": 3 + }, + { + "label": "Gorilla Mux", + "framework": "gorilla_mux", + "throughput": 2210706, + "nsOp": 452.3, + "bytes": 864, + "allocs": 8 + }, + { + "label": "httprouter", + "framework": "httprouter", + "throughput": 15683540, + "nsOp": 63.76, + "bytes": 16, + "allocs": 1 + } + ] + }, + { + "id": "path_param_json", + "rows": [ + { + "label": "GoForj Web", + "framework": "goforj_web", + "throughput": 5937455, + "nsOp": 168.4, + "bytes": 32, + "allocs": 2 + }, + { + "label": "net/http", + "framework": "net_http", + "throughput": 4358405, + "nsOp": 229.4, + "bytes": 48, + "allocs": 3 + }, + { + "label": "Echo", + "framework": "echo", + "throughput": 5351530, + "nsOp": 186.9, + "bytes": 32, + "allocs": 2 + }, + { + "label": "Gin", + "framework": "gin", + "throughput": 5302719, + "nsOp": 188.6, + "bytes": 48, + "allocs": 3 + }, + { + "label": "Chi", + "framework": "chi", + "throughput": 2257553, + "nsOp": 443, + "bytes": 736, + "allocs": 6 + }, + { + "label": "Gorilla Mux", + "framework": "gorilla_mux", + "throughput": 1548628, + "nsOp": 645.7, + "bytes": 1184, + "allocs": 10 + }, + { + "label": "httprouter", + "framework": "httprouter", + "throughput": 6261053, + "nsOp": 159.7, + "bytes": 64, + "allocs": 3 + } + ] + }, + { + "id": "middleware_chain", + "rows": [ + { + "label": "GoForj Web", + "framework": "goforj_web", + "throughput": 5834930, + "nsOp": 171.4, + "bytes": 32, + "allocs": 2 + }, + { + "label": "net/http", + "framework": "net_http", + "throughput": 3375985, + "nsOp": 296.2, + "bytes": 400, + "allocs": 4 + }, + { + "label": "Echo", + "framework": "echo", + "throughput": 2674599, + "nsOp": 373.9, + "bytes": 440, + "allocs": 8 + }, + { + "label": "Gin", + "framework": "gin", + "throughput": 3559858, + "nsOp": 280.9, + "bytes": 400, + "allocs": 4 + }, + { + "label": "Chi", + "framework": "chi", + "throughput": 2456688, + "nsOp": 407.1, + "bytes": 768, + "allocs": 6 + }, + { + "label": "Gorilla Mux", + "framework": "gorilla_mux", + "throughput": 1428773, + "nsOp": 699.9, + "bytes": 1320, + "allocs": 14 + }, + { + "label": "httprouter", + "framework": "httprouter", + "throughput": 4359708, + "nsOp": 229.4, + "bytes": 400, + "allocs": 4 + } + ] + } + ] + }, + "benchmarkLibraries": [ + { + "repo": "cache", + "benchmarks": 5 + }, + { + "repo": "crypt", + "benchmarks": 3 + }, + { + "repo": "events", + "benchmarks": 7 + }, + { + "repo": "execx", + "benchmarks": 2 + }, + { + "repo": "mail", + "benchmarks": 3 + }, + { + "repo": "metrics", + "benchmarks": 10 + }, + { + "repo": "queue", + "benchmarks": 6 + }, + { + "repo": "storage", + "benchmarks": 1 + }, + { + "repo": "str", + "benchmarks": 10 + }, + { + "repo": "web", + "benchmarks": 36 + }, + { + "repo": "wire", + "benchmarks": 2 + } + ], + "sources": [ + { + "repo": "cache", + "path": "docs/bench/benchmarks_rows.json", + "revision": "5e5d0f26e54b2e014e86630caa2ab72e6c8b0fe1", + "sha256": "560cbd0e2847840efe83a7dc474c6d77bb41585d305ee5878a287497fa630507" + }, + { + "repo": "events", + "path": "docs/bench/benchmarks_rows.json", + "revision": "21c6e503c3e99f4b95efa47c603daf87433e5af9", + "sha256": "3ca9808d2e0b5ab2796854e6c7d99bfa509e721a06ed4cddde929a71799ee679" + }, + { + "repo": "queue", + "path": "docs/bench/benchmarks_rows.json", + "revision": "439da9bbc0436f3ab8251dde0b296a1c3cf8a954", + "sha256": "ed230e76699758a15b044aad71038511888af093ca89377ab1e61703afb1b883" + }, + { + "repo": "storage", + "path": "docs/bench/benchmarks_rows.json", + "revision": "1ba3e4ca4bbdf19c4203c02413468dc934430c55", + "sha256": "b324cc04e5cb4d6fd9d7518c6e9b4400ac0c8fdb2ce7a10696e20669bb8a4391" + }, + { + "repo": "web", + "path": "docs/bench/benchmarks_rows.json", + "revision": "8ae67c788fbf8c8ac3c0d0c6ca73d5df221b3b2e", + "sha256": "2f4567eaef1f4246c60ed735e112172abb9c16e231c931b05c0a3281747f1eea" + } + ] +} diff --git a/docs/.vitepress/data/proof-stats.json b/docs/.vitepress/data/proof-stats.json index 37e40bc..2bced92 100644 --- a/docs/.vitepress/data/proof-stats.json +++ b/docs/.vitepress/data/proof-stats.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-08-01", + "generatedAt": "2026-08-07", "totals": { "unitTests": 1461, "integrationTests": 952, @@ -77,7 +77,7 @@ "integration": null, "testFns": 78, "benchmarks": 0, - "sourceRevision": "f2029e5346cf9e28eb01802cbbb3f77efef61dc7" + "sourceRevision": "1d70e18042d7e493eb0143e2b840b5207616bfab" }, { "repo": "cache", @@ -85,7 +85,7 @@ "integration": 118, "testFns": 240, "benchmarks": 5, - "sourceRevision": "5ab5e9cbabd399576026d337f50b9df62c00aa8e" + "sourceRevision": "7d4f8e96a3a91d4e2fb9dde4341f724983d49356" }, { "repo": "collection", @@ -93,7 +93,7 @@ "integration": null, "testFns": 380, "benchmarks": 0, - "sourceRevision": "a9c56c7894e2acc2bf0ce105cdd938dedc55ca9e" + "sourceRevision": "71f045125475ebed177e1576b4192f4fe26da1d8" }, { "repo": "console", @@ -101,7 +101,7 @@ "integration": null, "testFns": 212, "benchmarks": 0, - "sourceRevision": "e1883f6dcd15fdb6b98c45d26ca482c365d0b7d7" + "sourceRevision": "67d253dd50fc18d5ba1df8b7d361e441356ebf46" }, { "repo": "crypt", @@ -109,7 +109,7 @@ "integration": null, "testFns": 50, "benchmarks": 3, - "sourceRevision": "6011c59bec66fa3562878dbabaed50f21b707754" + "sourceRevision": "0882f0b3d558747357ad1b211e66db951b0c79e7" }, { "repo": "env", @@ -117,7 +117,7 @@ "integration": null, "testFns": 98, "benchmarks": 0, - "sourceRevision": "a243636d13c720753db746164307ec1327618515" + "sourceRevision": "2221d15bdbc425a9bc073063091675ee22228e10" }, { "repo": "events", @@ -125,7 +125,7 @@ "integration": 86, "testFns": 119, "benchmarks": 7, - "sourceRevision": "93b83a2bf8aafdd525406e04104b0d77a4a83ca8" + "sourceRevision": "02da1f5d049a3c94136548c06477476483280256" }, { "repo": "execx", @@ -133,7 +133,7 @@ "integration": null, "testFns": 129, "benchmarks": 2, - "sourceRevision": "2de4f085fa92416d133c472ef6e0d53857e5a3ed" + "sourceRevision": "c33b8b87490d17e7728ee45a184a179eacbd2660" }, { "repo": "godump", @@ -141,7 +141,7 @@ "integration": null, "testFns": 105, "benchmarks": 0, - "sourceRevision": "ee749adbc2002d28df33d1f0ae5d5afce77fb9e9" + "sourceRevision": "5b1ba91ab94161e900a06be3bac56543ac0ff30b" }, { "repo": "httpx", @@ -149,7 +149,7 @@ "integration": null, "testFns": 124, "benchmarks": 0, - "sourceRevision": "e6e073db6d275b38830028ae9024164bb68ee92d" + "sourceRevision": "2dc960f86f64103d2f9cc8b999322dab75800939" }, { "repo": "mail", @@ -157,7 +157,7 @@ "integration": null, "testFns": 88, "benchmarks": 3, - "sourceRevision": "d18e4698017bb016cf747c40f94f568ac920ba63" + "sourceRevision": "ac8693c05396f707b7b23028f441db472af6238b" }, { "repo": "metrics", @@ -165,7 +165,7 @@ "integration": null, "testFns": 61, "benchmarks": 10, - "sourceRevision": "49e7417dc921b77a63852d341d128ea249b4c24c" + "sourceRevision": "1381f232b3f4e7d400b808df1e5f8189b87f72b5" }, { "repo": "queue", @@ -173,7 +173,7 @@ "integration": 618, "testFns": 673, "benchmarks": 6, - "sourceRevision": "2e01ce48b1d61ae62042044656d7659f3c16cf24" + "sourceRevision": "0af045fa7888dae3dd8071d3b8f6a6af2175ef05" }, { "repo": "scheduler", @@ -181,7 +181,7 @@ "integration": null, "testFns": 80, "benchmarks": 0, - "sourceRevision": "08e837e8ef074a9ba36a315f3e9e6a25757fa354" + "sourceRevision": "2da693de562396dd030316f3aea464c80ec09c93" }, { "repo": "storage", @@ -189,7 +189,7 @@ "integration": 130, "testFns": 276, "benchmarks": 1, - "sourceRevision": "709a6540b007816282fa88a27344da60d73f0096" + "sourceRevision": "b2c6a3601f7764148f3c33d154b691ca2a3a59f1" }, { "repo": "str", @@ -197,7 +197,7 @@ "integration": null, "testFns": 96, "benchmarks": 10, - "sourceRevision": "e8402b9136fce20c4fa8d973fe3ae731e9cf2bcc" + "sourceRevision": "13862d9890b1d61d0f3b19e397eee214a34e8942" }, { "repo": "web", @@ -205,7 +205,7 @@ "integration": null, "testFns": 460, "benchmarks": 36, - "sourceRevision": "8ae67c788fbf8c8ac3c0d0c6ca73d5df221b3b2e" + "sourceRevision": "b1c88179b24ef67ccc51521e3b731a6cce0190a5" }, { "repo": "wire", @@ -213,7 +213,7 @@ "integration": null, "testFns": 132, "benchmarks": 2, - "sourceRevision": "8c74c4da7c0f4a47ad815ea61dc387d1e3c6ee80" + "sourceRevision": "a810d81ac3678288c8bb8c25396f581ee9e57b26" } ] } diff --git a/docs/.vitepress/theme/components/PerformanceStory.vue b/docs/.vitepress/theme/components/PerformanceStory.vue new file mode 100644 index 0000000..5a12ac0 --- /dev/null +++ b/docs/.vitepress/theme/components/PerformanceStory.vue @@ -0,0 +1,882 @@ + + + + + diff --git a/docs/index.md b/docs/index.md index 72297a2..8a2be97 100644 --- a/docs/index.md +++ b/docs/index.md @@ -677,7 +677,7 @@ func (w *Welcome) Greet(ctx context.Context, user User) error { :style="i ? { '--reveal-delay': `${i * 0.08}s` } : undefined" >{{ fmt(stat.count) }}{{ stat.suffix }}{{ stat.label }} -

Driver suites run against Redis, Postgres, MySQL, NATS, Kafka, MinIO, SQS, and more through testcontainers and emulators. These numbers are generated from the repositories, not written by hand: see how they are counted →

+

Driver suites run against Redis, Postgres, MySQL, NATS, Kafka, MinIO, SQS, and more through testcontainers and emulators. These numbers are generated from the repositories, not written by hand. See the performance story → Read the counting methodology →

diff --git a/docs/package.json b/docs/package.json index 4cc4583..54c9fa8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -3,6 +3,8 @@ "scripts": { "proof:refresh": "node ../bin/collect-proof-stats.mjs", "proof:check": "node ../bin/collect-proof-stats.mjs --check", + "performance:refresh": "node ../bin/collect-performance-stats.mjs", + "performance:check": "node ../bin/collect-performance-stats.mjs --check", "sync:design": "node .vitepress/scripts/sync-design-css.mjs", "audit:content": "node .vitepress/scripts/audit-content-value.mjs", "predev": "npm run sync:design", diff --git a/docs/performance.md b/docs/performance.md new file mode 100644 index 0000000..aca8252 --- /dev/null +++ b/docs/performance.md @@ -0,0 +1,13 @@ +--- +title: Performance +description: Source-backed GoForj library benchmarks across local and service-backed drivers. +sidebar: false +aside: false +noAutoTitle: true +--- + + + +