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
JavaScript
// 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;
}