|
| 1 | +import fs from 'node:fs'; |
| 2 | +import path from 'node:path'; |
| 3 | +import os from 'node:os'; |
| 4 | +import { generateFixture, FIXTURES } from './generate-fixtures.js'; |
| 5 | +import { createPackage, extractAll, listPackage, getRawHeader, uncache } from '../lib/asar.js'; |
| 6 | +import { readArchiveHeaderSync, readFilesystemSync } from '../lib/disk.js'; |
| 7 | + |
| 8 | +const config = FIXTURES.find((f) => f.name === 'many-small-files')!; |
| 9 | +const fixtureDir = generateFixture(config); |
| 10 | +const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'asar-prof-extract-')); |
| 11 | +const archiveFile = path.join(tmpDir, 'test.asar'); |
| 12 | +await createPackage(fixtureDir, archiveFile); |
| 13 | +const archiveSize = fs.statSync(archiveFile).size; |
| 14 | + |
| 15 | +console.log(`=== Extract breakdown: ${config.fileCount} files, archive ${(archiveSize / 1024).toFixed(0)} KB ===\n`); |
| 16 | + |
| 17 | +// 1. Header parsing |
| 18 | +{ |
| 19 | + const t = performance.now(); |
| 20 | + for (let i = 0; i < 10; i++) readArchiveHeaderSync(archiveFile); |
| 21 | + console.log(`header parse (avg): ${((performance.now() - t) / 10).toFixed(2)}ms`); |
| 22 | +} |
| 23 | + |
| 24 | +// 2. listFiles |
| 25 | +{ |
| 26 | + uncache(archiveFile); |
| 27 | + const filesystem = readFilesystemSync(archiveFile); |
| 28 | + const t = performance.now(); |
| 29 | + const files = filesystem.listFiles(); |
| 30 | + console.log(`listFiles: ${(performance.now() - t).toFixed(2)}ms (${files.length} entries)`); |
| 31 | + |
| 32 | + // 3. getFile lookups |
| 33 | + const followLinks = process.platform === 'win32'; |
| 34 | + const t2 = performance.now(); |
| 35 | + for (const fullPath of files) { |
| 36 | + const filename = fullPath.substr(1); |
| 37 | + filesystem.getFile(filename, followLinks); |
| 38 | + } |
| 39 | + console.log(`getFile (all): ${(performance.now() - t2).toFixed(2)}ms`); |
| 40 | +} |
| 41 | + |
| 42 | +// 4. Read all file data from archive with individual readSync |
| 43 | +{ |
| 44 | + uncache(archiveFile); |
| 45 | + const filesystem = readFilesystemSync(archiveFile); |
| 46 | + const files = filesystem.listFiles(); |
| 47 | + const followLinks = process.platform === 'win32'; |
| 48 | + const fd = fs.openSync(archiveFile, 'r'); |
| 49 | + const headerSize = filesystem.getHeaderSize(); |
| 50 | + |
| 51 | + let totalRead = 0; |
| 52 | + const t = performance.now(); |
| 53 | + for (const fullPath of files) { |
| 54 | + const filename = fullPath.substr(1); |
| 55 | + const file = filesystem.getFile(filename, followLinks); |
| 56 | + if ('size' in file && !('files' in file) && !('link' in file) && !file.unpacked) { |
| 57 | + const buffer = Buffer.alloc(file.size); |
| 58 | + if (file.size > 0) { |
| 59 | + const offset = 8 + headerSize + parseInt(file.offset); |
| 60 | + fs.readSync(fd, buffer, 0, file.size, offset); |
| 61 | + totalRead += file.size; |
| 62 | + } |
| 63 | + } |
| 64 | + } |
| 65 | + fs.closeSync(fd); |
| 66 | + console.log(`readSync (individual): ${(performance.now() - t).toFixed(2)}ms (${totalRead} bytes)`); |
| 67 | + |
| 68 | + // 5. Read all data in one shot |
| 69 | + const t2 = performance.now(); |
| 70 | + const fd2 = fs.openSync(archiveFile, 'r'); |
| 71 | + const dataStart = 8 + headerSize; |
| 72 | + const dataSize = archiveSize - dataStart; |
| 73 | + const allData = Buffer.alloc(dataSize); |
| 74 | + fs.readSync(fd2, allData, 0, dataSize, dataStart); |
| 75 | + fs.closeSync(fd2); |
| 76 | + console.log(`readSync (one shot): ${(performance.now() - t2).toFixed(2)}ms (${dataSize} bytes)`); |
| 77 | +} |
| 78 | + |
| 79 | +// 6. writeFileSync overhead |
| 80 | +{ |
| 81 | + const extractDir = path.join(tmpDir, 'write-test'); |
| 82 | + fs.mkdirSync(extractDir, { recursive: true }); |
| 83 | + |
| 84 | + // Pre-generate content |
| 85 | + const content = Buffer.alloc(256, 0x41); |
| 86 | + const filePaths: string[] = []; |
| 87 | + for (let i = 0; i < 10000; i++) { |
| 88 | + const dir = path.join(extractDir, `d${i % 10}`); |
| 89 | + fs.mkdirSync(dir, { recursive: true }); |
| 90 | + filePaths.push(path.join(dir, `f${i}.txt`)); |
| 91 | + } |
| 92 | + |
| 93 | + const t = performance.now(); |
| 94 | + for (const fp of filePaths) { |
| 95 | + fs.writeFileSync(fp, content); |
| 96 | + } |
| 97 | + console.log(`writeFileSync (10k): ${(performance.now() - t).toFixed(1)}ms`); |
| 98 | + |
| 99 | + fs.rmSync(extractDir, { recursive: true }); |
| 100 | +} |
| 101 | + |
| 102 | +// 7. mkdirSync overhead (with recursive) |
| 103 | +{ |
| 104 | + const extractDir = path.join(tmpDir, 'mkdir-test'); |
| 105 | + const dirs: string[] = []; |
| 106 | + for (let i = 0; i < 1000; i++) { |
| 107 | + dirs.push(path.join(extractDir, `a${i % 10}`, `b${i % 100}`, `c${i}`)); |
| 108 | + } |
| 109 | + const t = performance.now(); |
| 110 | + for (const d of dirs) { |
| 111 | + fs.mkdirSync(d, { recursive: true }); |
| 112 | + } |
| 113 | + console.log(`mkdirSync (1k): ${(performance.now() - t).toFixed(1)}ms`); |
| 114 | + fs.rmSync(extractDir, { recursive: true }); |
| 115 | +} |
| 116 | + |
| 117 | +// 8. Full extractAll for reference |
| 118 | +{ |
| 119 | + const extractDir = path.join(tmpDir, 'full-extract'); |
| 120 | + uncache(archiveFile); |
| 121 | + const t = performance.now(); |
| 122 | + extractAll(archiveFile, extractDir); |
| 123 | + console.log(`\nextractAll (total): ${(performance.now() - t).toFixed(1)}ms`); |
| 124 | + fs.rmSync(extractDir, { recursive: true }); |
| 125 | +} |
| 126 | + |
| 127 | +// 9. Pack insertFile loop overhead (without pre-read) |
| 128 | +{ |
| 129 | + console.log('\n--- Pack insert loop breakdown ---'); |
| 130 | + const { crawl } = await import('../lib/crawlfs.js'); |
| 131 | + const [filenames, metadata] = await crawl(fixtureDir + '/**/*', { dot: true }); |
| 132 | + |
| 133 | + // Just the insert loop with readFileSync + hash |
| 134 | + const { Filesystem } = await import('../lib/filesystem.js'); |
| 135 | + const filesystem = new Filesystem(fixtureDir); |
| 136 | + |
| 137 | + const fileEntries = filenames.filter((f) => metadata[f]?.type === 'file'); |
| 138 | + const dirEntries = filenames.filter((f) => metadata[f]?.type === 'directory'); |
| 139 | + |
| 140 | + let t = performance.now(); |
| 141 | + for (const d of dirEntries) filesystem.insertDirectory(d, false); |
| 142 | + console.log(`insertDirectory: ${(performance.now() - t).toFixed(1)}ms (${dirEntries.length})`); |
| 143 | + |
| 144 | + t = performance.now(); |
| 145 | + for (const f of fileEntries) { |
| 146 | + await filesystem.insertFile(f, () => fs.createReadStream(f), false, metadata[f]); |
| 147 | + } |
| 148 | + console.log(`insertFile (all): ${(performance.now() - t).toFixed(1)}ms (${fileEntries.length})`); |
| 149 | +} |
| 150 | + |
| 151 | +fs.rmSync(tmpDir, { recursive: true, force: true }); |
0 commit comments