| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- 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 ? `${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 no-await-in-loop */
- await new Promise((resolve) => {
- this._resolve = resolve;
- this._attach();
- this._worker.postMessage(task);
- });
- /* eslint-enable no-await-in-loop */
- }
- 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));
- /* eslint-disable no-await-in-loop */
- try {
- await target.execute();
- success += 1;
- } catch (e) {
- if (args.continue) {
- error(e);
- } else {
- exit(e);
- }
- }
- if (target.measures) {
- await measures.add(target.measures);
- }
- /* eslint-enable no-await-in-loop */
- }
- 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();
|