UNPKG

delta-sync

Version:

A lightweight framework for bi-directional database synchronization with automatic version tracking and conflict resolution.

540 lines (539 loc) 23.1 kB
// test/adapterPerformance.ts export class AdapterPerformanceTester { constructor(adapter, options = {}) { this.createdFileIds = []; this.createdItemIds = []; this.adapter = adapter; this.options = { testStoreName: options.testStoreName || 'perf_test', itemCount: options.itemCount || 100, iterations: options.iterations || 3, fileSize: options.fileSize || 1024 * 10, // 10KB concurrentOperations: options.concurrentOperations || 10, verbose: options.verbose !== undefined ? options.verbose : true, cleanupAfterTest: options.cleanupAfterTest !== undefined ? options.cleanupAfterTest : true }; this.testStoreName = this.options.testStoreName; } /** * 运行所有性能测试 */ async runAllTests() { console.log('=== 开始适配器性能测试 ==='); const results = {}; try { // 初始化适配器 await this.adapter.initSync(); // 基本CRUD性能 results.singleItemWrite = await this.testSingleItemWrite(); results.singleItemRead = await this.testSingleItemRead(); results.singleItemDelete = await this.testSingleItemDelete(); // 批量操作性能 results.bulkWrite = await this.testBulkWrite(); results.bulkRead = await this.testBulkRead(); results.bulkDelete = await this.testBulkDelete(); // 批量文件操作性能 results.bulkFileWrite = await this.testBulkFileWrite(); results.bulkFileRead = await this.testBulkFileRead(); results.bulkFileDelete = await this.testBulkFileDelete(); // 分页性能 results.pagination = await this.testPagination(); // 压力测试 results.stressTest = await this.testStress(); console.log('=== 适配器性能测试完成 ==='); // 计算总体得分(越低越好) const totalTimeMs = Object.values(results).reduce((sum, result) => sum + result.averageTimeMs, 0); const averageTimeMs = totalTimeMs / Object.keys(results).length; console.log(`总体平均操作时间: ${averageTimeMs.toFixed(2)}毫秒`); if (this.options.verbose) { console.log('\n详细结果:'); Object.entries(results).forEach(([testName, result]) => { console.log(`${testName}:`); console.log(` 平均时间: ${result.averageTimeMs.toFixed(2)}毫秒`); console.log(` 最小时间: ${result.minTimeMs.toFixed(2)}毫秒`); console.log(` 最大时间: ${result.maxTimeMs.toFixed(2)}毫秒`); if (result.operationsPerSecond) { console.log(` 每秒操作: ${result.operationsPerSecond.toFixed(2)}`); } if (result.throughputMBps) { console.log(` 吞吐量: ${result.throughputMBps.toFixed(2)}MB/秒`); } }); } // 如果配置了自动清理,则清理所有测试数据 if (this.options.cleanupAfterTest) { await this.cleanupTestData(); } return { success: true, results }; } catch (error) { console.error('性能测试过程中出现未捕获的错误:', error); // 尝试清理数据即使测试失败 if (this.options.cleanupAfterTest) { try { await this.cleanupTestData(); } catch (cleanupError) { console.error('清理测试数据时出错:', cleanupError); } } return { success: false, results }; } } /** * 测试单项写入性能 */ async testSingleItemWrite() { console.log('测试单项写入性能...'); const times = []; for (let i = 0; i < this.options.iterations; i++) { const itemId = `perf_single_write_${Date.now()}_${i}`; const item = { _delta_id: itemId, value: `测试值 ${i}`, _sync_status: 'pending' }; const startTime = performance.now(); await this.adapter.putBulk(this.testStoreName, [item]); const endTime = performance.now(); this.createdItemIds.push(itemId); times.push(endTime - startTime); } return this.calculatePerformanceResult(times, 1); } /** * 测试单项读取性能 */ async testSingleItemRead() { console.log('测试单项读取性能...'); const times = []; // 先创建测试项目 const testItemId = `perf_single_read_${Date.now()}`; const testItem = { _delta_id: testItemId, value: '测试读取值', _sync_status: 'pending' }; await this.adapter.putBulk(this.testStoreName, [testItem]); this.createdItemIds.push(testItemId); for (let i = 0; i < this.options.iterations; i++) { const startTime = performance.now(); await this.adapter.readBulk(this.testStoreName, [testItemId]); const endTime = performance.now(); times.push(endTime - startTime); } return this.calculatePerformanceResult(times, 1); } /** * 测试单项删除性能 */ async testSingleItemDelete() { console.log('测试单项删除性能...'); const times = []; for (let i = 0; i < this.options.iterations; i++) { // 创建要删除的项目 const itemId = `perf_single_delete_${Date.now()}_${i}`; const item = { _delta_id: itemId, value: `测试删除值 ${i}`, _sync_status: 'pending' }; await this.adapter.putBulk(this.testStoreName, [item]); const startTime = performance.now(); await this.adapter.deleteBulk(this.testStoreName, [itemId]); const endTime = performance.now(); times.push(endTime - startTime); } return this.calculatePerformanceResult(times, 1); } /** * 测试批量写入性能 */ async testBulkWrite() { console.log('测试批量写入性能...'); const times = []; for (let iter = 0; iter < this.options.iterations; iter++) { const items = []; for (let i = 0; i < this.options.itemCount; i++) { const itemId = `perf_bulk_write_${Date.now()}_${iter}_${i}`; items.push({ _delta_id: itemId, value: `批量写入测试值 ${i}`, _sync_status: 'pending', _ver: Date.now() + i }); this.createdItemIds.push(itemId); } const startTime = performance.now(); await this.adapter.putBulk(this.testStoreName, items); const endTime = performance.now(); times.push(endTime - startTime); } return this.calculatePerformanceResult(times, this.options.itemCount); } /** * 测试批量读取性能 */ async testBulkRead() { console.log('测试批量读取性能...'); const times = []; // 先创建测试项目 const testItems = []; const testItemIds = []; for (let i = 0; i < this.options.itemCount; i++) { const itemId = `perf_bulk_read_${Date.now()}_${i}`; testItems.push({ _delta_id: itemId, value: `批量读取测试值 ${i}`, _sync_status: 'pending' }); testItemIds.push(itemId); this.createdItemIds.push(itemId); } await this.adapter.putBulk(this.testStoreName, testItems); for (let i = 0; i < this.options.iterations; i++) { const startTime = performance.now(); await this.adapter.readBulk(this.testStoreName, testItemIds); const endTime = performance.now(); times.push(endTime - startTime); } return this.calculatePerformanceResult(times, this.options.itemCount); } /** * 测试批量删除性能 */ async testBulkDelete() { console.log('测试批量删除性能...'); const times = []; for (let iter = 0; iter < this.options.iterations; iter++) { // 创建要删除的项目 const items = []; const itemIds = []; for (let i = 0; i < this.options.itemCount; i++) { const itemId = `perf_bulk_delete_${Date.now()}_${iter}_${i}`; items.push({ _delta_id: itemId, value: `批量删除测试值 ${i}`, _sync_status: 'pending' }); itemIds.push(itemId); } await this.adapter.putBulk(this.testStoreName, items); const startTime = performance.now(); await this.adapter.deleteBulk(this.testStoreName, itemIds); const endTime = performance.now(); times.push(endTime - startTime); } return this.calculatePerformanceResult(times, this.options.itemCount); } /** * 测试批量文件写入性能 */ async testBulkFileWrite() { console.log('测试批量文件写入性能...'); const times = []; for (let iter = 0; iter < this.options.iterations; iter++) { const files = []; // 创建多个测试文件 for (let i = 0; i < this.options.itemCount; i++) { // 创建指定大小的测试文件 const testData = new Uint8Array(this.options.fileSize); // 填充随机数据 for (let j = 0; j < testData.length; j++) { testData[j] = Math.floor(Math.random() * 256); } const testBlob = new Blob([testData], { type: 'application/octet-stream' }); const fileId = `perf_bulk_file_write_${Date.now()}_${iter}_${i}.bin`; files.push({ content: testBlob, fileId: fileId }); } const startTime = performance.now(); const attachments = await this.adapter.saveFiles(files); const endTime = performance.now(); // 保存实际的文件ID用于后续清理 for (const attachment of attachments) { if (attachment && attachment.id) { this.createdFileIds.push(attachment.id); } } times.push(endTime - startTime); } return this.calculatePerformanceResult(times, this.options.itemCount, this.options.fileSize * this.options.itemCount); } /** * 测试批量文件读取性能 */ async testBulkFileRead() { console.log('测试批量文件读取性能...'); const times = []; // 创建测试文件 const files = []; const fileIds = []; for (let i = 0; i < this.options.itemCount; i++) { const testData = new Uint8Array(this.options.fileSize); // 填充随机数据 for (let j = 0; j < testData.length; j++) { testData[j] = Math.floor(Math.random() * 256); } const testBlob = new Blob([testData], { type: 'application/octet-stream' }); const fileId = `perf_bulk_file_read_${Date.now()}_${i}.bin`; files.push({ content: testBlob, fileId: fileId }); fileIds.push(fileId); } const attachments = await this.adapter.saveFiles(files); // 保存实际的文件ID用于后续清理和测试 const actualFileIds = []; for (let i = 0; i < attachments.length; i++) { const actualFileId = attachments[i]?.id || fileIds[i]; actualFileIds.push(actualFileId); this.createdFileIds.push(actualFileId); } for (let i = 0; i < this.options.iterations; i++) { const startTime = performance.now(); await this.adapter.readFiles(actualFileIds); const endTime = performance.now(); times.push(endTime - startTime); } return this.calculatePerformanceResult(times, this.options.itemCount, this.options.fileSize * this.options.itemCount); } /** * 测试批量文件删除性能 */ async testBulkFileDelete() { console.log('测试批量文件删除性能...'); const times = []; for (let iter = 0; iter < this.options.iterations; iter++) { // 创建要删除的文件 const files = []; const fileIds = []; for (let i = 0; i < this.options.itemCount; i++) { const testData = new Uint8Array(this.options.fileSize); const testBlob = new Blob([testData], { type: 'application/octet-stream' }); const fileId = `perf_bulk_file_delete_${Date.now()}_${iter}_${i}.bin`; files.push({ content: testBlob, fileId: fileId }); fileIds.push(fileId); } const attachments = await this.adapter.saveFiles(files); // 使用实际的文件ID进行删除测试 const actualFileIds = attachments.map(att => att.id); const startTime = performance.now(); await this.adapter.deleteFiles(actualFileIds); const endTime = performance.now(); times.push(endTime - startTime); } return this.calculatePerformanceResult(times, this.options.itemCount); } /** * 测试分页性能 */ async testPagination() { console.log('测试分页性能...'); const times = []; // 创建用于分页测试的数据 const items = []; for (let i = 0; i < this.options.itemCount; i++) { const itemId = `perf_pagination_${Date.now()}_${i}`; items.push({ _delta_id: itemId, value: `分页测试值 ${i}`, _sync_status: 'pending', _ver: Date.now() + i }); this.createdItemIds.push(itemId); } await this.adapter.putBulk(this.testStoreName, items); // 测试不同的页面大小 const pageSizes = [10, 25, 50]; for (const pageSize of pageSizes) { for (let i = 0; i < this.options.iterations; i++) { const startTime = performance.now(); await this.adapter.read(this.testStoreName, { limit: pageSize, offset: i * pageSize % this.options.itemCount }); const endTime = performance.now(); times.push(endTime - startTime); } } return this.calculatePerformanceResult(times, pageSizes[1]); // 使用中等页面大小计算每秒操作 } /** * 测试压力处理与并发操作 */ /** * 测试压力处理与并发操作 */ async testStress() { console.log('运行并发操作压力测试...'); const times = []; for (let iter = 0; iter < this.options.iterations; iter++) { const startTime = performance.now(); // 混合不同的操作 const operations = []; const stressItemIds = []; // 减少并发操作的数量,使测试更可靠 const reducedConcurrentOps = Math.min(this.options.concurrentOperations, 5); for (let i = 0; i < reducedConcurrentOps; i++) { const opType = i % 4; // 0 = 数据写入, 1 = 数据读取, 2 = 数据删除, 3 = 文件操作 if (opType === 0) { // 写入操作 const itemId = `stress_write_${Date.now()}_${i}`; const item = { _delta_id: itemId, value: `压力测试写入 ${i}`, _sync_status: 'pending' }; stressItemIds.push(itemId); this.createdItemIds.push(itemId); operations.push(this.adapter.putBulk(this.testStoreName, [item])); } else if (opType === 1) { // 读取操作(使用分页避免需要特定ID) operations.push(this.adapter.read(this.testStoreName, { limit: 5, offset: i * 5 % 20 })); } else if (opType === 2) { // 删除操作 - 先创建再删除 const itemId = `stress_delete_${Date.now()}_${i}`; const item = { _delta_id: itemId, value: `压力测试删除 ${i}`, _sync_status: 'pending' }; // 确保创建完成后再删除,避免竞态条件 await this.adapter.putBulk(this.testStoreName, [item]); operations.push(this.adapter.deleteBulk(this.testStoreName, [itemId])); } else { // 文件操作 - 分开处理文件保存和读取,避免并发操作同一文件 const testData = new Uint8Array(512); // 减小文件大小到512字节 const testBlob = new Blob([testData], { type: 'application/octet-stream' }); const fileId = `stress_file_${Date.now()}_${i}.bin`; try { // 先上传文件 const attachments = await this.adapter.saveFiles([{ content: testBlob, fileId }]); if (attachments[0]) { this.createdFileIds.push(attachments[0].id); // 然后尝试读取 operations.push(this.adapter.readFiles([attachments[0].id])); } } catch (error) { console.error(`文件操作失败 (${fileId}):`, error); // 继续测试,不中断 } } // 每个操作后添加小延迟,避免竞态条件 await new Promise(resolve => setTimeout(resolve, 20)); } // 依次执行操作,而不是并发执行 for (const operation of operations) { try { await operation; } catch (error) { console.warn('压力测试操作失败:', error); // 继续测试,不中断 } } const endTime = performance.now(); times.push(endTime - startTime); } return this.calculatePerformanceResult(times, this.options.concurrentOperations); } /** * 清理测试数据 */ async cleanupTestData() { console.log('正在清理测试数据...'); try { // 清理创建的项目 if (this.createdItemIds.length > 0) { // 分批处理,避免单次删除过多 const batchSize = 100; for (let i = 0; i < this.createdItemIds.length; i += batchSize) { const batch = this.createdItemIds.slice(i, i + batchSize); await this.adapter.deleteBulk(this.testStoreName, batch); } console.log(`已删除 ${this.createdItemIds.length} 个测试记录`); } // 清理创建的文件 if (this.createdFileIds.length > 0) { // 分批处理文件删除 const batchSize = 50; for (let i = 0; i < this.createdFileIds.length; i += batchSize) { const batch = this.createdFileIds.slice(i, i + batchSize); await this.adapter.deleteFiles(batch); } console.log(`已删除 ${this.createdFileIds.length} 个测试文件`); } // 查找并删除其他可能的测试数据 try { const result = await this.adapter.read(this.testStoreName, { limit: 1000 }); if (result && result.items.length > 0) { // 查找看起来像测试数据的项目 const testItems = result.items.filter(item => item._delta_id && (item._delta_id.includes('perf_') || item._delta_id.includes('test_') || item._delta_id.includes('bulk_') || item._delta_id.includes('stress_'))); if (testItems.length > 0) { // 删除发现的测试项目 await this.adapter.deleteBulk(this.testStoreName, testItems.map(item => item._delta_id)); console.log(`通过扫描删除了 ${testItems.length} 个额外测试记录`); } } } catch (error) { console.warn('扫描清理测试记录失败:', error); } console.log('清理完成'); } catch (error) { console.error('清理过程中出错:', error); throw error; } } /** * 根据原始计时数据计算性能指标 */ calculatePerformanceResult(times, operationsPerTest, dataSize) { const sumTime = times.reduce((sum, time) => sum + time, 0); const avgTime = sumTime / times.length; const minTime = Math.min(...times); const maxTime = Math.max(...times); const opsPerSecond = operationsPerTest * (1000 / avgTime); let throughput; if (dataSize) { // 计算 MB/s throughput = (dataSize / 1024 / 1024) / (avgTime / 1000); } return { averageTimeMs: avgTime, minTimeMs: minTime, maxTimeMs: maxTime, operationsPerSecond: opsPerSecond, throughputMBps: throughput }; } } export async function testAdapterPerformance(adapter, options) { const tester = new AdapterPerformanceTester(adapter, options); const result = await tester.runAllTests(); return result.results; }