mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-05-31 23:27:43 +00:00
### Description
This PR includes the following changes:
- upgrade js dependencies
- enable STRICT mode for web assembly build.
- corresponding fix for cmake-js upgrade
- corresponsing fix for linter upgrade
- upgrade default typescript compile option of:
- `moduleResolution`: from `node` to `node16`
- `target`: from `es2017` to `es2020`
- fix ESM module import in commonJS source file
## change explanation
### changes to onnxruntime_webassembly.cmake
`-s WASM=1` and `-s LLD_REPORT_UNDEFINED` in latest version is
by-default and deprecated.
### changes to onnxruntime_node.cmake
The npm package `cmake-js` updated its way to find file `node.lib`.
previously it downloads this file from Node.js public release channel,
and now it generates it from a definition file.
The node.js release channel does not contain a windows/arm64 version, so
previously cmake-js will fail to download `node.lib` for that platform.
this is why we made special handling to download the unofficial binary
to build. now this is no longer needed so we removed that from the cmake
file.
### changes to tsconfig.json
`node16` module resolution supports async import and `es2020` as target
supports top level await.
447 lines
14 KiB
TypeScript
447 lines
14 KiB
TypeScript
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
import {Env} from 'onnxruntime-common';
|
|
|
|
import {WebGLContext} from './backends/webgl/webgl-context';
|
|
|
|
export declare namespace Logger {
|
|
export interface SeverityTypeMap {
|
|
verbose: 'v';
|
|
info: 'i';
|
|
warning: 'w';
|
|
error: 'e';
|
|
fatal: 'f';
|
|
}
|
|
|
|
export type Severity = keyof SeverityTypeMap;
|
|
|
|
export type Provider = 'none'|'console';
|
|
|
|
/**
|
|
* Logging config that used to control the behavior of logger
|
|
*/
|
|
export interface Config {
|
|
/**
|
|
* Specify the logging provider. 'console' by default
|
|
*/
|
|
provider?: Provider;
|
|
/**
|
|
* Specify the minimal logger serverity. 'warning' by default
|
|
*/
|
|
minimalSeverity?: Logger.Severity;
|
|
/**
|
|
* Whether to output date time in log. true by default
|
|
*/
|
|
logDateTime?: boolean;
|
|
/**
|
|
* Whether to output source information (Not yet supported). false by default
|
|
*/
|
|
logSourceLocation?: boolean;
|
|
}
|
|
|
|
export interface CategorizedLogger {
|
|
verbose(content: string): void;
|
|
info(content: string): void;
|
|
warning(content: string): void;
|
|
error(content: string): void;
|
|
fatal(content: string): void;
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
|
export interface Logger {
|
|
(category: string): Logger.CategorizedLogger;
|
|
|
|
verbose(content: string): void;
|
|
verbose(category: string, content: string): void;
|
|
info(content: string): void;
|
|
info(category: string, content: string): void;
|
|
warning(content: string): void;
|
|
warning(category: string, content: string): void;
|
|
error(content: string): void;
|
|
error(category: string, content: string): void;
|
|
fatal(content: string): void;
|
|
fatal(category: string, content: string): void;
|
|
|
|
/**
|
|
* Reset the logger configuration.
|
|
* @param config specify an optional default config
|
|
*/
|
|
reset(config?: Logger.Config): void;
|
|
/**
|
|
* Set the logger's behavior on the given category
|
|
* @param category specify a category string. If '*' is specified, all previous configuration will be overwritten. If
|
|
* '' is specified, the default behavior will be updated.
|
|
* @param config the config object to indicate the logger's behavior
|
|
*/
|
|
set(category: string, config: Logger.Config): void;
|
|
|
|
/**
|
|
* Set the logger's behavior from ort-common env
|
|
* @param env the env used to set logger. Currently only setting loglevel is supported through Env.
|
|
*/
|
|
setWithEnv(env: Env): void;
|
|
}
|
|
|
|
interface LoggerProvider {
|
|
log(severity: Logger.Severity, content: string, category?: string): void;
|
|
}
|
|
class NoOpLoggerProvider implements LoggerProvider {
|
|
log(_severity: Logger.Severity, _content: string, _category?: string) {
|
|
// do nothing
|
|
}
|
|
}
|
|
class ConsoleLoggerProvider implements LoggerProvider {
|
|
log(severity: Logger.Severity, content: string, category?: string) {
|
|
// eslint-disable-next-line no-console
|
|
console.log(`${this.color(severity)} ${category ? '\x1b[35m' + category + '\x1b[0m ' : ''}${content}`);
|
|
}
|
|
|
|
private color(severity: Logger.Severity) {
|
|
switch (severity) {
|
|
case 'verbose':
|
|
return '\x1b[34;40mv\x1b[0m';
|
|
case 'info':
|
|
return '\x1b[32mi\x1b[0m';
|
|
case 'warning':
|
|
return '\x1b[30;43mw\x1b[0m';
|
|
case 'error':
|
|
return '\x1b[31;40me\x1b[0m';
|
|
case 'fatal':
|
|
return '\x1b[101mf\x1b[0m';
|
|
default:
|
|
throw new Error(`unsupported severity: ${severity}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const SEVERITY_VALUE = {
|
|
verbose: 1000,
|
|
info: 2000,
|
|
warning: 4000,
|
|
error: 5000,
|
|
fatal: 6000
|
|
};
|
|
|
|
const LOGGER_PROVIDER_MAP: {readonly [provider: string]: Readonly<LoggerProvider>} = {
|
|
['none']: new NoOpLoggerProvider(),
|
|
['console']: new ConsoleLoggerProvider()
|
|
};
|
|
const LOGGER_DEFAULT_CONFIG = {
|
|
provider: 'console',
|
|
minimalSeverity: 'warning',
|
|
logDateTime: true,
|
|
logSourceLocation: false
|
|
};
|
|
let LOGGER_CONFIG_MAP:
|
|
{[category: string]: Readonly<Required<Logger.Config>>} = {['']: LOGGER_DEFAULT_CONFIG as Required<Logger.Config>};
|
|
|
|
function log(category: string): Logger.CategorizedLogger;
|
|
function log(severity: Logger.Severity, content: string): void;
|
|
function log(severity: Logger.Severity, category: string, content: string): void;
|
|
function log(severity: Logger.Severity, arg1: string, arg2?: string): void;
|
|
function log(
|
|
arg0: string|Logger.Severity, arg1?: string, arg2?: string|number, arg3?: number): Logger.CategorizedLogger|void {
|
|
if (arg1 === undefined) {
|
|
// log(category: string): Logger.CategorizedLogger;
|
|
return createCategorizedLogger(arg0);
|
|
} else if (arg2 === undefined) {
|
|
// log(severity, content);
|
|
logInternal(arg0 as Logger.Severity, arg1, 1);
|
|
} else if (typeof arg2 === 'number' && arg3 === undefined) {
|
|
// log(severity, content, stack)
|
|
logInternal(arg0 as Logger.Severity, arg1, arg2);
|
|
} else if (typeof arg2 === 'string' && arg3 === undefined) {
|
|
// log(severity, category, content)
|
|
logInternal(arg0 as Logger.Severity, arg2, 1, arg1);
|
|
} else if (typeof arg2 === 'string' && typeof arg3 === 'number') {
|
|
// log(severity, category, content, stack)
|
|
logInternal(arg0 as Logger.Severity, arg2, arg3, arg1);
|
|
} else {
|
|
throw new TypeError('input is valid');
|
|
}
|
|
}
|
|
|
|
function createCategorizedLogger(category: string): Logger.CategorizedLogger {
|
|
return {
|
|
verbose: log.verbose.bind(null, category),
|
|
info: log.info.bind(null, category),
|
|
warning: log.warning.bind(null, category),
|
|
error: log.error.bind(null, category),
|
|
fatal: log.fatal.bind(null, category)
|
|
};
|
|
}
|
|
|
|
// NOTE: argument 'category' is put the last parameter beacause typescript
|
|
// doesn't allow optional argument put in front of required argument. This
|
|
// order is different from a usual logging API.
|
|
function logInternal(severity: Logger.Severity, content: string, stack: number, category?: string) {
|
|
const config = LOGGER_CONFIG_MAP[category || ''] || LOGGER_CONFIG_MAP[''];
|
|
if (SEVERITY_VALUE[severity] < SEVERITY_VALUE[config.minimalSeverity]) {
|
|
return;
|
|
}
|
|
|
|
if (config.logDateTime) {
|
|
content = `${new Date().toISOString()}|${content}`;
|
|
}
|
|
|
|
if (config.logSourceLocation) {
|
|
// TODO: calculate source location from 'stack'
|
|
}
|
|
|
|
LOGGER_PROVIDER_MAP[config.provider].log(severity, content, category);
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
namespace log {
|
|
export function verbose(content: string): void;
|
|
export function verbose(category: string, content: string): void;
|
|
export function verbose(arg0: string, arg1?: string) {
|
|
log('verbose', arg0, arg1);
|
|
}
|
|
export function info(content: string): void;
|
|
export function info(category: string, content: string): void;
|
|
export function info(arg0: string, arg1?: string) {
|
|
log('info', arg0, arg1);
|
|
}
|
|
export function warning(content: string): void;
|
|
export function warning(category: string, content: string): void;
|
|
export function warning(arg0: string, arg1?: string) {
|
|
log('warning', arg0, arg1);
|
|
}
|
|
export function error(content: string): void;
|
|
export function error(category: string, content: string): void;
|
|
export function error(arg0: string, arg1?: string) {
|
|
log('error', arg0, arg1);
|
|
}
|
|
export function fatal(content: string): void;
|
|
export function fatal(category: string, content: string): void;
|
|
export function fatal(arg0: string, arg1?: string) {
|
|
log('fatal', arg0, arg1);
|
|
}
|
|
|
|
export function reset(config?: Logger.Config): void {
|
|
LOGGER_CONFIG_MAP = {};
|
|
set('', config || {});
|
|
}
|
|
export function set(category: string, config: Logger.Config): void {
|
|
if (category === '*') {
|
|
reset(config);
|
|
} else {
|
|
const previousConfig = LOGGER_CONFIG_MAP[category] || LOGGER_DEFAULT_CONFIG;
|
|
LOGGER_CONFIG_MAP[category] = {
|
|
provider: config.provider || previousConfig.provider,
|
|
minimalSeverity: config.minimalSeverity || previousConfig.minimalSeverity,
|
|
logDateTime: (config.logDateTime === undefined) ? previousConfig.logDateTime : config.logDateTime,
|
|
logSourceLocation: (config.logSourceLocation === undefined) ? previousConfig.logSourceLocation :
|
|
config.logSourceLocation
|
|
};
|
|
}
|
|
|
|
// TODO: we want to support wildcard or regex?
|
|
}
|
|
|
|
export function setWithEnv(env: Env): void {
|
|
const config: Logger.Config = {};
|
|
if (env.logLevel) {
|
|
config.minimalSeverity = env.logLevel as Logger.Severity;
|
|
}
|
|
set('', config);
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-redeclare, @typescript-eslint/naming-convention
|
|
export const Logger: Logger = log;
|
|
|
|
export declare namespace Profiler {
|
|
export interface Config {
|
|
maxNumberEvents?: number;
|
|
flushBatchSize?: number;
|
|
flushIntervalInMilliseconds?: number;
|
|
}
|
|
|
|
export type EventCategory = 'session'|'node'|'op'|'backend';
|
|
|
|
export interface Event {
|
|
end(): void|Promise<void>;
|
|
}
|
|
}
|
|
// TODO
|
|
// class WebGLEvent implements Profiler.Event {}
|
|
|
|
class Event implements Profiler.Event {
|
|
constructor(
|
|
public category: Profiler.EventCategory, public name: string, public startTime: number,
|
|
private endCallback: (e: Event) => void|Promise<void>, public timer?: WebGLQuery, public ctx?: WebGLContext) {}
|
|
|
|
async end() {
|
|
return this.endCallback(this);
|
|
}
|
|
|
|
async checkTimer(): Promise<number> {
|
|
if (this.ctx === undefined || this.timer === undefined) {
|
|
throw new Error('No webgl timer found');
|
|
} else {
|
|
this.ctx.endTimer();
|
|
return this.ctx.waitForQueryAndGetTime(this.timer);
|
|
}
|
|
}
|
|
}
|
|
|
|
class EventRecord {
|
|
constructor(
|
|
public category: Profiler.EventCategory, public name: string, public startTime: number, public endTime: number) {}
|
|
}
|
|
|
|
export class Profiler {
|
|
static create(config?: Profiler.Config): Profiler {
|
|
if (config === undefined) {
|
|
return new this();
|
|
}
|
|
return new this(config.maxNumberEvents, config.flushBatchSize, config.flushIntervalInMilliseconds);
|
|
}
|
|
|
|
private constructor(maxNumberEvents?: number, flushBatchSize?: number, flushIntervalInMilliseconds?: number) {
|
|
this._started = false;
|
|
this._maxNumberEvents = maxNumberEvents === undefined ? 10000 : maxNumberEvents;
|
|
this._flushBatchSize = flushBatchSize === undefined ? 10 : flushBatchSize;
|
|
this._flushIntervalInMilliseconds = flushIntervalInMilliseconds === undefined ? 5000 : flushIntervalInMilliseconds;
|
|
}
|
|
|
|
// start profiling
|
|
start() {
|
|
this._started = true;
|
|
this._timingEvents = [];
|
|
this._flushTime = now();
|
|
this._flushPointer = 0;
|
|
}
|
|
|
|
// stop profiling
|
|
stop() {
|
|
this._started = false;
|
|
for (; this._flushPointer < this._timingEvents.length; this._flushPointer++) {
|
|
this.logOneEvent(this._timingEvents[this._flushPointer]);
|
|
}
|
|
}
|
|
|
|
// create an event scope for the specific function
|
|
event<T>(category: Profiler.EventCategory, name: string, func: () => T, ctx?: WebGLContext): T;
|
|
event<T>(category: Profiler.EventCategory, name: string, func: () => Promise<T>, ctx?: WebGLContext): Promise<T>;
|
|
|
|
event<T>(category: Profiler.EventCategory, name: string, func: () => T | Promise<T>, ctx?: WebGLContext): T
|
|
|Promise<T> {
|
|
const event = this._started ? this.begin(category, name, ctx) : undefined;
|
|
let isPromise = false;
|
|
|
|
const res = func();
|
|
|
|
// we consider a then-able object is a promise
|
|
if (res && typeof (res as Promise<T>).then === 'function') {
|
|
isPromise = true;
|
|
return new Promise<T>((resolve, reject) => {
|
|
(res as Promise<T>)
|
|
.then(
|
|
async value => { // fulfilled
|
|
if (event) {
|
|
await event.end();
|
|
}
|
|
resolve(value);
|
|
},
|
|
async reason => { // rejected
|
|
if (event) {
|
|
await event.end();
|
|
}
|
|
reject(reason);
|
|
});
|
|
});
|
|
}
|
|
if (!isPromise && event) {
|
|
const eventRes = event.end();
|
|
if (eventRes && typeof eventRes.then === 'function') {
|
|
return new Promise<T>((resolve, reject) => {
|
|
(eventRes).then(
|
|
() => { // fulfilled
|
|
resolve(res);
|
|
},
|
|
(reason) => { // rejected
|
|
reject(reason);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// begin an event
|
|
begin(category: Profiler.EventCategory, name: string, ctx?: WebGLContext): Event {
|
|
if (!this._started) {
|
|
throw new Error('profiler is not started yet');
|
|
}
|
|
if (ctx === undefined) {
|
|
const startTime = now();
|
|
this.flush(startTime);
|
|
return new Event(category, name, startTime, e => this.endSync(e));
|
|
} else {
|
|
const timer: WebGLQuery = ctx.beginTimer();
|
|
return new Event(category, name, 0, async e => this.end(e), timer, ctx);
|
|
}
|
|
}
|
|
|
|
// end the specific event
|
|
private async end(event: Event): Promise<void> {
|
|
const endTime: number = await event.checkTimer();
|
|
if (this._timingEvents.length < this._maxNumberEvents) {
|
|
this._timingEvents.push(new EventRecord(event.category, event.name, event.startTime, endTime));
|
|
this.flush(endTime);
|
|
}
|
|
}
|
|
|
|
private endSync(event: Event): void {
|
|
const endTime: number = now();
|
|
if (this._timingEvents.length < this._maxNumberEvents) {
|
|
this._timingEvents.push(new EventRecord(event.category, event.name, event.startTime, endTime));
|
|
this.flush(endTime);
|
|
}
|
|
}
|
|
|
|
private logOneEvent(event: EventRecord) {
|
|
Logger.verbose(
|
|
`Profiler.${event.category}`,
|
|
`${(event.endTime - event.startTime).toFixed(2)}ms on event '${event.name}' at ${event.endTime.toFixed(2)}`);
|
|
}
|
|
|
|
private flush(currentTime: number) {
|
|
if (this._timingEvents.length - this._flushPointer >= this._flushBatchSize ||
|
|
currentTime - this._flushTime >= this._flushIntervalInMilliseconds) {
|
|
// should flush when either batch size accumlated or interval elepsed
|
|
|
|
for (const previousPointer = this._flushPointer; this._flushPointer < previousPointer + this._flushBatchSize &&
|
|
this._flushPointer < this._timingEvents.length;
|
|
this._flushPointer++) {
|
|
this.logOneEvent(this._timingEvents[this._flushPointer]);
|
|
}
|
|
|
|
this._flushTime = now();
|
|
}
|
|
}
|
|
|
|
get started() {
|
|
return this._started;
|
|
}
|
|
private _started = false;
|
|
private _timingEvents: EventRecord[];
|
|
|
|
private readonly _maxNumberEvents: number;
|
|
|
|
private readonly _flushBatchSize: number;
|
|
private readonly _flushIntervalInMilliseconds: number;
|
|
|
|
private _flushTime: number;
|
|
private _flushPointer = 0;
|
|
}
|
|
|
|
/**
|
|
* returns a number to represent the current timestamp in a resolution as high as possible.
|
|
*/
|
|
export const now = (typeof performance !== 'undefined' && performance.now) ? () => performance.now() : Date.now;
|