ag-psd 32.0.0 → 34.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/d13d7610 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
- package/CHANGELOG.md +100 -0
- package/LICENSE +5 -0
- package/README.md +4 -7
- package/README_PSD.md +261 -84
- package/TODO.md +13 -84
- package/dist/abr.d.ts +11 -4
- package/dist/abr.js +362 -230
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +56 -2
- package/dist/additionalInfo.js +828 -361
- package/dist/additionalInfo.js.map +1 -1
- package/dist/ase.d.ts +6 -1
- package/dist/ase.js +61 -61
- package/dist/ase.js.map +1 -1
- package/dist/bundle.js +3321 -1364
- package/dist/descriptor.d.ts +28 -13
- package/dist/descriptor.js +295 -241
- package/dist/descriptor.js.map +1 -1
- package/dist/helpers.d.ts +7 -1
- package/dist/helpers.js +246 -4
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.d.ts +15 -3
- package/dist/imageResources.js +586 -144
- package/dist/imageResources.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jpeg.js +8 -8
- package/dist/jpeg.js.map +1 -1
- package/dist/psd.d.ts +292 -19
- package/dist/psd.js.map +1 -1
- package/dist/psdReader.d.ts +10 -0
- package/dist/psdReader.js +424 -216
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +2 -0
- package/dist/psdWriter.js +373 -113
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.d.ts +32 -1
- package/dist/text.js +75 -6
- package/dist/text.js.map +1 -1
- package/dist/utf8.js +0 -3
- package/dist/utf8.js.map +1 -1
- package/dist-es/abr.d.ts +11 -4
- package/dist-es/abr.js +362 -230
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +56 -2
- package/dist-es/additionalInfo.js +828 -361
- package/dist-es/additionalInfo.js.map +1 -1
- package/dist-es/ase.d.ts +6 -1
- package/dist-es/ase.js +61 -61
- package/dist-es/ase.js.map +1 -1
- package/dist-es/descriptor.d.ts +28 -13
- package/dist-es/descriptor.js +295 -241
- package/dist-es/descriptor.js.map +1 -1
- package/dist-es/helpers.d.ts +7 -1
- package/dist-es/helpers.js +246 -4
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.d.ts +15 -3
- package/dist-es/imageResources.js +586 -144
- package/dist-es/imageResources.js.map +1 -1
- package/dist-es/index.d.ts +1 -0
- package/dist-es/index.js +1 -0
- package/dist-es/index.js.map +1 -1
- package/dist-es/jpeg.js +8 -8
- package/dist-es/jpeg.js.map +1 -1
- package/dist-es/psd.d.ts +292 -19
- package/dist-es/psd.js.map +1 -1
- package/dist-es/psdReader.d.ts +10 -0
- package/dist-es/psdReader.js +424 -216
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +2 -0
- package/dist-es/psdWriter.js +373 -113
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.d.ts +32 -1
- package/dist-es/text.js +75 -6
- package/dist-es/text.js.map +1 -1
- package/dist-es/utf8.js +0 -3
- package/dist-es/utf8.js.map +1 -1
- package/package.json +2 -2
- package/perf.js +312 -9
- package/src/abr.ts +382 -235
- package/src/additionalInfo.ts +1071 -482
- package/src/ase.ts +66 -61
- package/src/descriptor.ts +291 -242
- package/src/helpers.ts +266 -3
- package/src/imageResources.ts +736 -169
- package/src/index.ts +1 -0
- package/src/jpeg.ts +8 -8
- package/src/psd.ts +271 -32
- package/src/psdReader.ts +440 -221
- package/src/psdWriter.ts +402 -110
- package/src/text.ts +93 -7
- package/src/utf8.ts +0 -4
package/perf.js
CHANGED
|
@@ -1,12 +1,315 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// Measures decoding speed of all PSD/PSB files in the repository and resources/psd-files
|
|
2
|
+
// and saves the summary to results/perf/perf-<version>[-<tag>].json
|
|
3
|
+
//
|
|
4
|
+
// usage: npx tsc -p tsconfig.json && node perf.js [options]
|
|
5
|
+
// --iterations <n> timed runs per file, after one warmup run (default 3)
|
|
6
|
+
// --filter <text> only measure files with path containing text
|
|
7
|
+
// --tag <name> appended to summary file name (perf-<version>-<name>.json)
|
|
8
|
+
// --compare <file> summary file (or version/tag, e.g. 33.0.0) to compare with,
|
|
9
|
+
// defaults to most recently modified other summary in results/perf
|
|
10
|
+
// --threshold <n> relative change reported as regression/improvement (default 0.1 = 10%)
|
|
5
11
|
|
|
6
|
-
const
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const crypto = require('crypto');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const v8 = require('v8');
|
|
17
|
+
const vm = require('vm');
|
|
7
18
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
19
|
+
const root = __dirname;
|
|
20
|
+
const perfDir = path.join(root, 'results', 'perf');
|
|
21
|
+
const excludedDirs = ['node_modules', 'dist', 'dist-es', 'results', 'resources', 'coverage', 'coverage-remapped', '.git'];
|
|
22
|
+
const readOptions = { useImageData: true, log: () => { } };
|
|
23
|
+
const minDiffMs = 2; // ignore smaller absolute changes as noise
|
|
11
24
|
|
|
12
|
-
|
|
25
|
+
function parseArgs() {
|
|
26
|
+
const args = { iterations: 3, filter: '', tag: '', compare: '', threshold: 0.1 };
|
|
27
|
+
const argv = process.argv.slice(2);
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < argv.length; i++) {
|
|
30
|
+
const key = argv[i].replace(/^--/, '');
|
|
31
|
+
if (!(key in args)) throw new Error(`Unknown option: ${argv[i]}`);
|
|
32
|
+
const value = argv[++i];
|
|
33
|
+
if (value === undefined) throw new Error(`Missing value for: ${argv[i - 1]}`);
|
|
34
|
+
args[key] = typeof args[key] === 'number' ? +value : value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return args;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findFiles(dir, result = []) {
|
|
41
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
42
|
+
const fullPath = path.join(dir, entry.name);
|
|
43
|
+
|
|
44
|
+
if (entry.isDirectory()) {
|
|
45
|
+
if (!excludedDirs.includes(entry.name)) findFiles(fullPath, result);
|
|
46
|
+
} else if (/\.ps[db]$/i.test(entry.name)) {
|
|
47
|
+
result.push(fullPath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function collectFiles(filter) {
|
|
55
|
+
const repoFiles = findFiles(root).sort();
|
|
56
|
+
const resourcesDir = path.join(root, 'resources', 'psd-files');
|
|
57
|
+
const resourceFiles = fs.existsSync(resourcesDir) ? findFiles(resourcesDir).sort() : [];
|
|
58
|
+
const hashes = new Set();
|
|
59
|
+
const files = [];
|
|
60
|
+
|
|
61
|
+
for (const file of [...repoFiles, ...resourceFiles]) {
|
|
62
|
+
const relative = path.relative(root, file).replace(/\\/g, '/');
|
|
63
|
+
if (filter && !relative.includes(filter)) continue;
|
|
64
|
+
|
|
65
|
+
const hash = crypto.createHash('sha1').update(fs.readFileSync(file)).digest('hex');
|
|
66
|
+
if (hashes.has(hash)) continue; // same file exists in both locations
|
|
67
|
+
|
|
68
|
+
hashes.add(hash);
|
|
69
|
+
files.push({ path: relative, fullPath: file, hash });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return files;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function checkBuild() {
|
|
76
|
+
const index = path.join(root, 'dist', 'index.js');
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(index)) {
|
|
79
|
+
console.error('dist/index.js not found, build first: npx tsc -p tsconfig.json');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const buildTime = fs.statSync(index).mtimeMs;
|
|
84
|
+
const stale = fs.readdirSync(path.join(root, 'src'))
|
|
85
|
+
.filter(f => f.endsWith('.ts') && fs.statSync(path.join(root, 'src', f)).mtimeMs > buildTime);
|
|
86
|
+
|
|
87
|
+
if (stale.length) console.warn(`WARNING: dist/ is older than source files (${stale.join(', ')}), rebuild first: npx tsc -p tsconfig.json\n`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getGC() {
|
|
91
|
+
v8.setFlagsFromString('--expose-gc');
|
|
92
|
+
return vm.runInNewContext('gc');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function median(values) {
|
|
96
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
97
|
+
const mid = sorted.length >> 1;
|
|
98
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const round = (value, digits = 2) => +value.toFixed(digits);
|
|
102
|
+
|
|
103
|
+
function formatSize(bytes) {
|
|
104
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
105
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
106
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function measure(readPsd, file, iterations, gc) {
|
|
110
|
+
const buffer = fs.readFileSync(file.fullPath);
|
|
111
|
+
const result = { path: file.path, hash: file.hash, size: buffer.byteLength, status: 'ok' };
|
|
112
|
+
const times = [];
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i <= iterations; i++) {
|
|
115
|
+
gc();
|
|
116
|
+
const start = performance.now();
|
|
117
|
+
let psd;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
psd = readPsd(buffer, readOptions);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
result.status = 'error';
|
|
123
|
+
result.error = `${e.message || e}`.split('\n')[0].slice(0, 200);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const time = performance.now() - start;
|
|
127
|
+
if (i > 0) times.push(time); // first run is warmup
|
|
128
|
+
|
|
129
|
+
if (psd && i === 0) {
|
|
130
|
+
result.width = psd.width;
|
|
131
|
+
result.height = psd.height;
|
|
132
|
+
result.layers = countLayers(psd);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
psd = undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
result.min = round(Math.min(...times), 3);
|
|
139
|
+
result.median = round(median(times), 3);
|
|
140
|
+
result.times = times.map(t => round(t, 3));
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function countLayers(parent) {
|
|
145
|
+
let count = 0;
|
|
146
|
+
|
|
147
|
+
for (const child of parent.children || []) {
|
|
148
|
+
count += 1 + countLayers(child);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return count;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function summarize(results, elapsed) {
|
|
155
|
+
const ok = results.filter(r => r.status === 'ok');
|
|
156
|
+
const totalBytes = ok.reduce((sum, r) => sum + r.size, 0);
|
|
157
|
+
const totalMin = ok.reduce((sum, r) => sum + r.min, 0);
|
|
158
|
+
const totalMedian = ok.reduce((sum, r) => sum + r.median, 0);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
files: results.length,
|
|
162
|
+
ok: ok.length,
|
|
163
|
+
failed: results.length - ok.length,
|
|
164
|
+
totalBytes,
|
|
165
|
+
totalMinMs: round(totalMin),
|
|
166
|
+
totalMedianMs: round(totalMedian),
|
|
167
|
+
throughputMBps: round(totalBytes / (1024 * 1024) / (totalMin / 1000)),
|
|
168
|
+
failedTotalMinMs: round(results.filter(r => r.status !== 'ok').reduce((sum, r) => sum + r.min, 0)),
|
|
169
|
+
elapsedMs: round(elapsed),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function findCompareFile(compare, outputFile) {
|
|
174
|
+
if (compare) {
|
|
175
|
+
const candidates = [compare, path.join(perfDir, compare), path.join(perfDir, `perf-${compare}.json`)];
|
|
176
|
+
const found = candidates.find(f => fs.existsSync(f) && fs.statSync(f).isFile());
|
|
177
|
+
if (!found) throw new Error(`Summary to compare not found: ${compare}`);
|
|
178
|
+
return found;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!fs.existsSync(perfDir)) return undefined;
|
|
182
|
+
|
|
183
|
+
return fs.readdirSync(perfDir)
|
|
184
|
+
.filter(f => /^perf-.*\.json$/.test(f))
|
|
185
|
+
.map(f => path.join(perfDir, f))
|
|
186
|
+
.filter(f => path.resolve(f) !== path.resolve(outputFile))
|
|
187
|
+
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function printTable(rows, columns) {
|
|
191
|
+
const widths = columns.map((c, i) => Math.max(c.length, ...rows.map(r => `${r[i]}`.length)));
|
|
192
|
+
const line = cells => cells.map((c, i) => i === 0 ? `${c}`.padEnd(widths[i]) : `${c}`.padStart(widths[i])).join(' ');
|
|
193
|
+
console.log(line(columns));
|
|
194
|
+
for (const row of rows) console.log(line(row));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function compareSummaries(current, previousFile, threshold) {
|
|
198
|
+
const previous = JSON.parse(fs.readFileSync(previousFile, 'utf8'));
|
|
199
|
+
const previousByHash = new Map(previous.results.map(r => [r.hash, r]));
|
|
200
|
+
const changes = [];
|
|
201
|
+
const statusChanges = [];
|
|
202
|
+
let currentTotal = 0, previousTotal = 0, logRatioSum = 0;
|
|
203
|
+
|
|
204
|
+
for (const r of current.results) {
|
|
205
|
+
const p = previousByHash.get(r.hash);
|
|
206
|
+
if (!p) continue;
|
|
207
|
+
|
|
208
|
+
if (p.status !== r.status) {
|
|
209
|
+
statusChanges.push(`${r.path}: ${p.status} -> ${r.status}${r.error ? ` (${r.error})` : ''}`);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (r.status !== 'ok') continue;
|
|
214
|
+
|
|
215
|
+
currentTotal += r.min;
|
|
216
|
+
previousTotal += p.min;
|
|
217
|
+
logRatioSum += Math.log(Math.max(r.min, 0.001) / Math.max(p.min, 0.001));
|
|
218
|
+
changes.push({ path: r.path, before: p.min, after: r.min, ratio: r.min / Math.max(p.min, 0.001) });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const significant = c => Math.abs(c.after - c.before) >= minDiffMs && Math.abs(c.ratio - 1) >= threshold;
|
|
222
|
+
const regressions = changes.filter(c => c.ratio > 1 && significant(c)).sort((a, b) => (b.after - b.before) - (a.after - a.before));
|
|
223
|
+
const improvements = changes.filter(c => c.ratio < 1 && significant(c)).sort((a, b) => (a.after - a.before) - (b.after - b.before));
|
|
224
|
+
const percent = ratio => `${ratio >= 1 ? '+' : ''}${((ratio - 1) * 100).toFixed(1)}%`;
|
|
225
|
+
const toRows = list => list.slice(0, 20).map(c => [c.path, c.before.toFixed(2), c.after.toFixed(2), percent(c.ratio)]);
|
|
226
|
+
|
|
227
|
+
console.log(`\nComparison with ${path.relative(root, previousFile)} (version ${previous.version}${previous.tag ? `-${previous.tag}` : ''}, ${previous.date})`);
|
|
228
|
+
console.log(` common files: ${changes.length}`);
|
|
229
|
+
console.log(` total (min): ${previousTotal.toFixed(1)} ms -> ${currentTotal.toFixed(1)} ms (${percent(currentTotal / Math.max(previousTotal, 0.001))})`);
|
|
230
|
+
console.log(` geometric mean of per-file ratios: ${percent(Math.exp(logRatioSum / Math.max(changes.length, 1)))}`);
|
|
231
|
+
|
|
232
|
+
if (previous.node !== current.node || previous.cpu !== current.cpu) {
|
|
233
|
+
console.log(` NOTE: different environment (${previous.node}, ${previous.cpu})`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (statusChanges.length) {
|
|
237
|
+
console.log(`\nStatus changes (${statusChanges.length}):`);
|
|
238
|
+
for (const s of statusChanges) console.log(` ${s}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (regressions.length) {
|
|
242
|
+
console.log(`\nRegressions (${regressions.length}, showing top 20 by ms):`);
|
|
243
|
+
printTable(toRows(regressions), ['file', 'before', 'after', 'change']);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (improvements.length) {
|
|
247
|
+
console.log(`\nImprovements (${improvements.length}, showing top 20 by ms):`);
|
|
248
|
+
printTable(toRows(improvements), ['file', 'before', 'after', 'change']);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!regressions.length && !improvements.length && !statusChanges.length) {
|
|
252
|
+
console.log(`\nNo significant changes (threshold ${threshold * 100}%, ${minDiffMs} ms)`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function main() {
|
|
257
|
+
const args = parseArgs();
|
|
258
|
+
checkBuild();
|
|
259
|
+
|
|
260
|
+
require('./dist/initializeCanvas');
|
|
261
|
+
const { readPsd } = require('./dist/index');
|
|
262
|
+
const { version } = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
263
|
+
const outputFile = path.join(perfDir, `perf-${version}${args.tag ? `-${args.tag}` : ''}${args.filter ? '-filtered' : ''}.json`);
|
|
264
|
+
const gc = getGC();
|
|
265
|
+
const files = collectFiles(args.filter);
|
|
266
|
+
const results = [];
|
|
267
|
+
const start = performance.now();
|
|
268
|
+
|
|
269
|
+
console.log(`ag-psd ${version}, ${files.length} files, ${args.iterations} iterations, node ${process.version}`);
|
|
270
|
+
|
|
271
|
+
for (let i = 0; i < files.length; i++) {
|
|
272
|
+
if (process.stdout.isTTY) {
|
|
273
|
+
process.stdout.write(`\r[${i + 1}/${files.length}] ${files[i].path}`.slice(0, process.stdout.columns - 1).padEnd(process.stdout.columns - 1));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
results.push(measure(readPsd, files[i], args.iterations, gc));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (process.stdout.isTTY) process.stdout.write('\r'.padEnd(process.stdout.columns - 1) + '\r');
|
|
280
|
+
|
|
281
|
+
const summary = {
|
|
282
|
+
version,
|
|
283
|
+
tag: args.tag || undefined,
|
|
284
|
+
filter: args.filter || undefined,
|
|
285
|
+
date: new Date().toISOString(),
|
|
286
|
+
node: process.version,
|
|
287
|
+
platform: `${os.platform()} ${os.release()} ${os.arch()}`,
|
|
288
|
+
cpu: os.cpus()[0]?.model.trim(),
|
|
289
|
+
iterations: args.iterations,
|
|
290
|
+
readOptions: { useImageData: true },
|
|
291
|
+
total: summarize(results, performance.now() - start),
|
|
292
|
+
results,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
fs.mkdirSync(perfDir, { recursive: true });
|
|
296
|
+
fs.writeFileSync(outputFile, JSON.stringify(summary, null, 2), 'utf8');
|
|
297
|
+
|
|
298
|
+
const { total } = summary;
|
|
299
|
+
console.log(`\nfiles: ${total.files} (${total.ok} ok, ${total.failed} failed)`);
|
|
300
|
+
console.log(`total size: ${formatSize(total.totalBytes)}`);
|
|
301
|
+
console.log(`total time: ${total.totalMinMs.toFixed(1)} ms (min), ${total.totalMedianMs.toFixed(1)} ms (median)`);
|
|
302
|
+
console.log(`throughput: ${total.throughputMBps} MB/s`);
|
|
303
|
+
console.log(`elapsed: ${(total.elapsedMs / 1000).toFixed(1)} s`);
|
|
304
|
+
|
|
305
|
+
console.log('\nSlowest files:');
|
|
306
|
+
const slowest = results.filter(r => r.status === 'ok').sort((a, b) => b.min - a.min).slice(0, 10);
|
|
307
|
+
printTable(slowest.map(r => [r.path, formatSize(r.size), r.min.toFixed(2), r.median.toFixed(2)]), ['file', 'size', 'min ms', 'median ms']);
|
|
308
|
+
|
|
309
|
+
console.log(`\nSummary saved to ${path.relative(root, outputFile)}`);
|
|
310
|
+
|
|
311
|
+
const compareFile = findCompareFile(args.compare, outputFile);
|
|
312
|
+
if (compareFile) compareSummaries(summary, compareFile, args.threshold);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
main();
|