testing local-first datastores
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 129 lines 5.1 kB view raw
1import { writeFile, mkdir } from 'fs/promises'; 2import { join } from 'path'; 3import type { StoreBenchmarkResults, BenchmarkResult } from './types.js'; 4 5function formatDuration(ms: number): string { 6 if (ms < 1000) return `${ms.toFixed(0)}ms`; 7 if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`; 8 return `${(ms / 60000).toFixed(2)}m`; 9} 10 11function formatBytes(bytes: number): string { 12 if (bytes < 1024) return `${bytes} B`; 13 if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; 14 if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; 15 return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; 16} 17 18function formatThroughput(bytes: number, ms: number): string { 19 const bytesPerSec = (bytes / ms) * 1000; 20 return `${formatBytes(bytesPerSec)}/s`; 21} 22 23function padRight(str: string, len: number): string { 24 return str.length >= len ? str : str + ' '.repeat(len - str.length); 25} 26 27function padLeft(str: string, len: number): string { 28 return str.length >= len ? str : ' '.repeat(len - str.length) + str; 29} 30 31function printBenchmark(b: BenchmarkResult, indent = ''): void { 32 if (b.failed) { 33 console.log(`${indent}${padRight(b.name, 30)} ${padLeft('FAILED', 10)} ${b.error || 'Unknown error'}`); 34 return; 35 } 36 let line = `${indent}${padRight(b.name, 30)} ${padLeft(formatDuration(b.durationMs), 10)}`; 37 if (b.itemCount !== undefined) { 38 line += ` (${b.itemCount} items)`; 39 } 40 if (b.bytesProcessed !== undefined) { 41 line += ` ${formatBytes(b.bytesProcessed)}`; 42 line += ` @ ${formatThroughput(b.bytesProcessed, b.durationMs)}`; 43 } 44 console.log(line); 45} 46 47function formatResultOrFailed(b: BenchmarkResult, formatter: (b: BenchmarkResult) => string): string { 48 if (b.failed) return 'FAILED'; 49 return formatter(b); 50} 51 52export function printResults(results: StoreBenchmarkResults[]): void { 53 console.log('\n' + '='.repeat(80)); 54 console.log('BENCHMARK RESULTS'); 55 console.log('='.repeat(80)); 56 57 for (const r of results) { 58 console.log(`\n${'─'.repeat(80)}`); 59 console.log(`Store: ${r.storeName}`); 60 console.log('─'.repeat(80)); 61 62 console.log('\nINIT:'); 63 printBenchmark(r.init, ' '); 64 65 console.log('\nWRITES:'); 66 printBenchmark(r.writes.allUrls, ' '); 67 printBenchmark(r.writes.allMetadata, ' '); 68 printBenchmark(r.writes.allImages, ' '); 69 printBenchmark(r.writes.allDocuments, ' '); 70 71 console.log('\nREADS:'); 72 printBenchmark(r.reads.recentUrls, ' '); 73 printBenchmark(r.reads.randomImages, ' '); 74 printBenchmark(r.reads.randomDocuments, ' '); 75 76 console.log('\nDISK:'); 77 console.log(` Total storage: ${formatBytes(r.disk.totalBytes)}`); 78 } 79 80 // Comparison table if multiple stores 81 if (results.length > 1) { 82 console.log('\n' + '='.repeat(80)); 83 console.log('COMPARISON'); 84 console.log('='.repeat(80)); 85 86 const headers = ['Metric', ...results.map(r => r.storeName)]; 87 const colWidth = 15; 88 89 console.log('\n' + headers.map(h => padRight(h, colWidth)).join(' │ ')); 90 console.log('─'.repeat(colWidth * headers.length + (headers.length - 1) * 3)); 91 92 const metrics = [ 93 { name: 'Init', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.init, b => formatDuration(b.durationMs)) }, 94 { name: 'Write URLs', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.writes.allUrls, b => formatDuration(b.durationMs)) }, 95 { name: 'Write metadata', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.writes.allMetadata, b => formatDuration(b.durationMs)) }, 96 { name: 'Write images', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.writes.allImages, b => formatDuration(b.durationMs)) }, 97 { name: 'Write docs', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.writes.allDocuments, b => formatDuration(b.durationMs)) }, 98 { name: 'Read URLs', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.reads.recentUrls, b => formatDuration(b.durationMs)) }, 99 { name: 'Read images', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.reads.randomImages, b => formatDuration(b.durationMs)) }, 100 { name: 'Read docs', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.reads.randomDocuments, b => formatDuration(b.durationMs)) }, 101 { name: 'Disk usage', get: (r: StoreBenchmarkResults) => formatBytes(r.disk.totalBytes) }, 102 ]; 103 104 for (const metric of metrics) { 105 const row = [padRight(metric.name, colWidth), ...results.map(r => padRight(metric.get(r), colWidth))]; 106 console.log(row.join(' │ ')); 107 } 108 } 109 110 console.log('\n'); 111} 112 113export async function saveResults(results: StoreBenchmarkResults[], baseDir = 'results'): Promise<string> { 114 const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); 115 const runDir = join(baseDir, `benchmark-${timestamp}`); 116 117 await mkdir(runDir, { recursive: true }); 118 119 await writeFile( 120 join(runDir, 'results.json'), 121 JSON.stringify({ 122 timestamp: new Date().toISOString(), 123 results 124 }, null, 2) 125 ); 126 127 console.log(`Results saved to ${runDir}/results.json`); 128 return runDir; 129}