Files
wehub-resource-sync 7254f7b4d1
Build / Build (macos-latest) (push) Waiting to run
Build / Build (ubuntu-latest) (push) Waiting to run
Build / Build (windows-latest) (push) Waiting to run
Build / Analyze (javascript) (push) Waiting to run
Build / Analyze (python) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:37:45 +08:00

394 lines
13 KiB
JavaScript
Executable File

import * as fs from 'fs/promises';
import * as inspector from 'inspector';
import * as os from 'os';
import * as path from 'path';
import * as process from 'process';
import * as url from 'url';
import * as worker_threads from 'worker_threads';
const clearLine = () => {
if (process.stdout.clearLine) {
process.stdout.clearLine();
}
};
const write = (message) => {
if (process.stdout.write) {
process.stdout.write(message);
}
};
const access = async (path) => {
try {
await fs.access(path);
return true;
} catch {
return false;
}
};
const error = (e) => {
/* eslint-disable no-console */
console.error(`${e.name}: ${e.message}`);
if (e.stack) {
// console.error(e.stack);
}
if (e.cause) {
console.error(` ${e.cause.name}: ${e.cause.message}`);
}
};
const exit = (e) => {
error(e);
/* eslint-enable no-console */
process.exit(1);
};
const dirname = (...args) => {
const file = url.fileURLToPath(import.meta.url);
const dir = path.dirname(file);
return path.join(dir, ...args);
};
const configuration = async () => {
const file = dirname('models.json');
const content = await fs.readFile(file, 'utf-8');
return JSON.parse(content);
};
class Logger {
constructor(threads) {
this._threads = threads;
this._entries = new Map();
}
update(identifier, message) {
let value = null;
if (message) {
switch (message.name) {
case 'name':
delete this._cache;
clearLine();
write(`${message.target}\n`);
value = '';
break;
case 'download':
if (message.percent !== undefined) {
value = `${(` ${Math.floor(100 * message.percent)}`).slice(-3)}% `;
} else if (Number.isInteger(message.position)) {
value = ` ${message.position}${this._threads === 1 ? ' bytes' : ''} `;
} else {
value = ' \u2714 ';
}
break;
case 'decompress':
value = this._threads === 1 ? 'decompress' : ' ^ ';
break;
case 'write':
value = this._threads === 1 ? 'write' : ' * ';
break;
default:
throw new Error(`Unsupported status message '${message.name}'.`);
}
}
if (!this._entries.has(identifier) || this._entries.get(identifier) !== value) {
this._entries.set(identifier, value);
this._flush();
}
}
delete(identifier) {
this._entries.delete(identifier);
this._flush();
}
flush() {
delete this._cache;
this._flush();
}
_flush() {
const values = Array.from(this._entries.values());
const text = values.some((s) => s) ? ` ${values.map((s) => s || ' ').join('-')}\r` : '';
if (this._cache !== text) {
this._cache = text;
clearLine();
write(text);
}
}
}
class Queue extends Array {
constructor(targets, patterns) {
for (const target of targets) {
target.targets = target.target.split(',');
target.name = target.type ? `third_party/test/${target.type}/${target.targets[0]}` : target.targets[0];
target.tags = target.tags ? target.tags.split(',') : [];
}
if (patterns.length > 0) {
const tags = new Set();
patterns = patterns.filter((pattern) => {
if (pattern.startsWith('tag:')) {
tags.add(pattern.substring(4));
return false;
}
return true;
});
patterns = patterns.map((pattern) => {
const wildcard = pattern.indexOf('*') !== -1;
return new RegExp(`^${wildcard ? `${pattern.replace(/\*/g, '.*')}$` : pattern}`);
});
targets = targets.filter((target) => {
for (const file of target.targets) {
const value = target.type ? `${target.type}/${file}` : file;
if (patterns.some((pattern) => pattern.test(value))) {
return true;
}
if (target.tags.some((tag) => tags.has(tag))) {
return true;
}
}
return false;
});
}
super(...targets.reverse());
}
}
class Table {
constructor(schema) {
this.schema = schema;
const line = `${Array.from(this.schema).join(',')}\n`;
this.content = [line];
this.entries = [];
}
async add(row) {
this.entries.push(row);
row = new Map(row);
const line = `${Array.from(this.schema).map((key) => {
const value = row.has(key) ? row.get(key) : '';
row.delete(key);
return value;
}).join(',')}\n`;
if (row.size > 0) {
throw new Error();
}
this.content.push(line);
if (this.file) {
await fs.appendFile(this.file, line);
}
}
async log(file) {
if (file) {
await fs.mkdir(path.dirname(file), { recursive: true });
await fs.writeFile(file, this.content.join(''));
this.file = file;
}
}
summarize(name) {
const entries = this.entries.filter((entry) => entry.has(name));
return entries.map((entry) => entry.get(name)).reduce((a, c) => a + c, 0);
}
}
class Worker {
constructor(identifier, queue, logger, measures) {
this._identifier = identifier;
this._queue = queue;
this._logger = logger;
this._measures = measures;
}
async start() {
this._events = {};
this._events.message = (message) => this._message(message);
this._events.error = (error) => this._error(error);
this._worker = new worker_threads.Worker('./test/worker.js');
for (let task = this._queue.pop(); task; task = this._queue.pop()) {
task.measures = this._measures ? new Map() : null;
this._logger.update(this._identifier, null);
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
this._resolve = resolve;
this._attach();
this._worker.postMessage(task);
});
}
this._logger.delete(this._identifier);
await this._worker.terminate();
}
_attach() {
this._worker.on('message', this._events.message);
this._worker.on('error', this._events.error);
}
_detach() {
this._worker.off('message', this._events.message);
this._worker.off('error', this._events.error);
}
async _message(message) {
switch (message.type) {
case 'status': {
this._logger.update(this._identifier, message);
break;
}
case 'error': {
write(`\n${message.target}\n`);
this._error(message.error);
break;
}
case 'complete': {
if (this._measures) {
await this._measures.add(message.measures);
}
this._detach();
this._resolve();
delete this._resolve;
break;
}
default: {
throw new Error(`Unsupported message type '${message.type}'.`);
}
}
}
_error(error) {
this._detach();
delete this._resolve;
exit(error);
}
}
const main = async () => {
try {
const args = { inputs: [], measure: false, profile: false, continue: false, serial: false };
if (process.argv.length > 2) {
for (const arg of process.argv.slice(2)) {
switch (arg) {
case 'measure': args.measure = true; args.serial = true; break;
case 'profile': args.profile = true; args.serial = true; break;
case 'continue': args.continue = true; args.serial = true; break;
default: {
// eslint-disable-next-line no-await-in-loop
const exists = await access(arg);
if (exists || !/[*?[\]]/.test(arg)) {
args.inputs.push(arg);
} else {
const iterator = fs.glob(arg, { cwd: process.cwd() });
// eslint-disable-next-line no-await-in-loop
const files = await Array.fromAsync(iterator);
args.inputs.push(...files);
}
break;
}
}
}
}
const exists = await Promise.all(args.inputs.map((pattern) => access(pattern)));
const paths = exists.length > 0 && exists.every((value) => value);
const patterns = paths ? [] : args.inputs;
const targets = paths ? args.inputs.map((path) => ({ target: path, tags: 'quantization,validation' })) : await configuration();
const queue = new Queue(targets, patterns);
const threads = args.serial || inspector.url() ? 1 : undefined;
const logger = new Logger(threads);
let measures = null;
if (args.measure) {
measures = new Table(['name', 'download', 'load', 'validate', 'render']);
await measures.log(dirname('..', 'dist', 'test', 'measures.csv'));
}
let session = null;
if (args.profile) {
session = new inspector.Session();
session.connect();
await new Promise((resolve, reject) => {
session.post('Profiler.enable', (error) => error ? reject(error) : resolve());
});
await new Promise((resolve, reject) => {
session.post('Profiler.start', (error) => error ? reject(error) : resolve());
});
/* eslint-disable no-console */
console.profile();
/* eslint-enable no-console */
}
if (threads === 1) {
const worker = await import('./worker.js');
const total = queue.length;
let success = 0;
for (let item = queue.pop(); item; item = queue.pop()) {
const target = new worker.Target(item);
target.measures = measures ? new Map() : null;
target.serial = args.serial;
target.on('status', (sender, message) => logger.update('', message));
try {
// eslint-disable-next-line no-await-in-loop
await target.execute();
success += 1;
} catch (e) {
if (args.continue) {
error(e);
} else {
exit(e);
}
}
if (target.measures) {
// eslint-disable-next-line no-await-in-loop
await measures.add(target.measures);
}
}
if (args.continue) {
write(` ${success} / ${total} = ${success * 100 / total}%\n`);
}
} else {
const threads = Math.max(1, Math.min(10, Math.round(0.7 * os.cpus().length), queue.length));
const identifiers = [...new Array(threads).keys()].map((value) => value.toString());
const workers = identifiers.map((identifier) => new Worker(identifier, queue, logger, measures));
const promises = workers.map((worker) => worker.start());
await Promise.all(promises);
}
if (args.measure) {
const values = {
download: measures.summarize('download'),
load: measures.summarize('load'),
validate: measures.summarize('validate'),
render: measures.summarize('render')
};
values.total = values.load + values.validate + values.render;
const pad1 = Math.max(...Object.keys(values).map((key) => key.length));
const pad2 = Math.max(...Object.values(values).map((value) => value.toFixed(2).indexOf('.')));
write('\n');
for (let [key, value] of Object.entries(values)) {
key = `${key}:`.padEnd(pad1 + 1);
value = `${value.toFixed(2)}`.padStart(pad2 + 3);
write(`${key} ${value}\n`);
}
write('\n');
}
if (args.profile) {
/* eslint-disable no-console */
console.profileEnd();
/* eslint-enable no-console */
const data = await new Promise((resolve, reject) => {
session.post('Profiler.stop', (error, data) => error ? reject(error) : resolve(data));
});
session.disconnect();
const file = dirname('..', 'dist', 'test', 'profile.cpuprofile');
await fs.mkdir(path.dirname(file), { recursive: true });
await fs.writeFile(file, JSON.stringify(data.profile), 'utf-8');
}
} catch (error) {
exit(error);
}
};
await main();