pokerogue/scripts/optimize-json.js

301 lines
11 KiB
JavaScript

import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { glob } from 'glob';
import os from 'os';
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const sourceDir = 'public';
const SMALL_FILE_THRESHOLD = 5 * 1024; // 5KB
// 动态计算最佳参数
async function calculateOptimalParams() {
const totalMemory = os.totalmem();
const freeMemory = os.freemem();
const cpuCount = os.cpus().length;
const cpuUsage = os.loadavg()[0] / cpuCount;
// 根据系统内存使用情况动态调整内存使用比例
const memoryUsageRatio = 1 - (freeMemory / totalMemory);
let memoryAllocationRatio;
if (memoryUsageRatio < 0.7) {
memoryAllocationRatio = 0.4;
} else if (memoryUsageRatio < 0.85) {
memoryAllocationRatio = 0.3;
} else {
memoryAllocationRatio = 0.2;
}
// 估算单个文件处理的平均内存占用
const estimatedMemoryPerFile = Math.max(
256 * 1024, // 最小256KB
Math.min(
1 * 1024 * 1024, // 最大1MB
Math.floor(totalMemory / (1024 * cpuCount))
)
);
// 计算批处理大小
const memoryForProcessing = totalMemory * memoryAllocationRatio;
let batchSize = Math.floor(memoryForProcessing / (estimatedMemoryPerFile * cpuCount));
// 动态调整批处理大小的上下限
const minBatchSize = Math.max(50, Math.floor(200 / cpuCount));
const maxBatchSize = Math.min(
2000,
Math.floor(memoryForProcessing / (256 * 1024))
);
batchSize = Math.max(minBatchSize, Math.min(maxBatchSize, batchSize));
// 优化工作线程数量
let workerCount;
const maxThreads = cpuCount * 2;
const systemLoad = os.loadavg()[0] / cpuCount;
const memoryConstraint = memoryUsageRatio > 0.85;
if (memoryConstraint) {
workerCount = Math.max(2, Math.min(cpuCount - 1, 4));
} else if (systemLoad < 0.5) {
workerCount = Math.max(2, Math.min(maxThreads, 6));
} else if (systemLoad < 1.0) {
workerCount = Math.max(2, Math.min(cpuCount + 2, 6));
} else {
workerCount = Math.max(2, Math.min(cpuCount, 4));
}
if (batchSize < 100) {
workerCount = Math.min(workerCount, 2);
} else if (batchSize < 200) {
workerCount = Math.min(workerCount, 3);
}
return {
batchSize,
workerCount,
systemInfo: {
totalMemory: formatBytes(totalMemory),
freeMemory: formatBytes(freeMemory),
cpuCount,
cpuUsage: (cpuUsage * 100).toFixed(1) + '%',
memoryUsage: (memoryUsageRatio * 100).toFixed(1) + '%',
memoryAllocationRatio: (memoryAllocationRatio * 100).toFixed(1) + '%',
estimatedMemoryPerFile: formatBytes(estimatedMemoryPerFile)
}
};
}
// 格式化字节数
function formatBytes(bytes) {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)}${units[unitIndex]}`;
}
// 工作线程逻辑
if (!isMainThread) {
const { files, sourceDir } = workerData;
async function optimizeJson(inputPath) {
const relativePath = path.relative(sourceDir, inputPath);
const tempPath = `${inputPath}.temp`;
try {
const inputStats = await fs.stat(inputPath);
const isSmallFile = inputStats.size < SMALL_FILE_THRESHOLD;
// 读取JSON文件
const jsonContent = await fs.readFile(inputPath, 'utf8');
let jsonData;
try {
jsonData = JSON.parse(jsonContent);
} catch (error) {
return {
success: false,
path: relativePath,
error: '无效的JSON文件'
};
}
// 压缩JSON
const optimizedJson = JSON.stringify(jsonData);
// 写入临时文件
await fs.writeFile(tempPath, optimizedJson, 'utf8');
const outputStats = await fs.stat(tempPath);
if (outputStats.size < inputStats.size) {
// 如果优化后的文件更小,则替换原文件
await fs.rename(tempPath, inputPath);
return {
success: true,
inputSize: inputStats.size,
outputSize: outputStats.size,
path: relativePath
};
} else {
// 如果优化后的文件更大,则删除临时文件
await fs.unlink(tempPath);
return {
success: true,
inputSize: inputStats.size,
outputSize: inputStats.size,
path: relativePath,
skipped: true
};
}
} catch (error) {
// 清理临时文件
try {
await fs.unlink(tempPath);
} catch {}
return {
success: false,
path: relativePath,
error: error.message
};
}
}
Promise.all(files.map(file => optimizeJson(file)))
.then(results => parentPort.postMessage(results));
}
// 主线程逻辑
else {
async function processJsonFiles() {
try {
// 计算最佳参数
const optimalParams = await calculateOptimalParams();
console.log('\n========== 系统资源信息 ==========');
console.log(`总内存: ${optimalParams.systemInfo.totalMemory}`);
console.log(`可用内存: ${optimalParams.systemInfo.freeMemory}`);
console.log(`CPU核心数: ${optimalParams.systemInfo.cpuCount}`);
console.log(`CPU使用率: ${optimalParams.systemInfo.cpuUsage}`);
console.log(`内存使用率: ${optimalParams.systemInfo.memoryUsage}`);
console.log(`内存分配比例: ${optimalParams.systemInfo.memoryAllocationRatio}`);
console.log(`估计每个文件内存占用: ${optimalParams.systemInfo.estimatedMemoryPerFile}`);
console.log(`优化批次大小: ${optimalParams.batchSize}`);
console.log(`工作线程数: ${optimalParams.workerCount}`);
console.log('==================================\n');
// 获取所有JSON文件
const files = await glob(path.join(sourceDir, '**/*.json'));
const totalFiles = files.length;
console.log(`找到 ${files.length} 个JSON文件需要优化\n`);
// 初始化统计数据
let totalOriginalSize = 0;
let totalOptimizedSize = 0;
let successCount = 0;
let failCount = 0;
let skippedCount = 0;
const startTime = Date.now();
const results = [];
// 将文件分成多个批次
const batches = [];
for (let i = 0; i < files.length; i += optimalParams.batchSize) {
batches.push(files.slice(i, Math.min(i + optimalParams.batchSize, files.length)));
}
let batchIndex = 0;
// 处理每个批次
const processBatch = async () => {
if (batchIndex >= batches.length) return null;
const currentBatch = batches[batchIndex++];
const worker = new Worker(new URL(import.meta.url), {
workerData: {
files: currentBatch,
sourceDir
}
});
return new Promise((resolve, reject) => {
worker.on('message', (batchResults) => {
results.push(...batchResults);
// 更新进度
const progress = Math.round((results.length / totalFiles) * 100);
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1);
const estimatedTotal = (elapsedTime / progress * 100).toFixed(1);
process.stdout.write(`\r处理进度: ${progress}% (${results.length}/${totalFiles}) - 已用时间: ${elapsedTime}秒 - 预计总时间: ${estimatedTotal}`);
worker.terminate();
resolve();
});
worker.on('error', (err) => {
worker.terminate();
reject(err);
});
worker.on('exit', (code) => {
if (code !== 0 && !worker.exitCode) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
});
};
// 并行处理所有批次
while (batchIndex < batches.length) {
const workerPromises = [];
for (let i = 0; i < optimalParams.workerCount && batchIndex < batches.length; i++) {
workerPromises.push(processBatch());
}
await Promise.all(workerPromises);
}
// 统计结果
for (const result of results) {
if (result.success) {
successCount++;
totalOriginalSize += result.inputSize;
totalOptimizedSize += result.outputSize;
if (result.skipped) {
skippedCount++;
}
} else {
failCount++;
console.error(`\n✗ 处理 ${result.path} 时出错:`, result.error);
}
}
// 打印报告
const endTime = Date.now();
const duration = ((endTime - startTime) / 1000).toFixed(2);
const totalSavings = ((totalOriginalSize - totalOptimizedSize) / totalOriginalSize * 100).toFixed(2);
console.log('\n\n========== 优化结果报告 ==========');
console.log(`处理总文件数: ${totalFiles}`);
console.log(`成功处理: ${successCount} 个文件`);
console.log(`跳过处理: ${skippedCount} 个文件(优化后体积更大)`);
console.log(`处理失败: ${failCount} 个文件`);
console.log(`原始总大小: ${(totalOriginalSize / 1024 / 1024).toFixed(2)}MB`);
console.log(`优化后总大小: ${(totalOptimizedSize / 1024 / 1024).toFixed(2)}MB`);
console.log(`总体积减少: ${totalSavings}%`);
console.log(`处理耗时: ${duration}`);
console.log(`平均处理速度: ${(totalFiles / duration).toFixed(2)}个/秒`);
console.log('================================\n');
} catch (error) {
console.error('处理过程中发生错误:', error);
}
}
processJsonFiles();
}