mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-05-18 21:21:17 +00:00
### Description
See
454996d496
for manual changes (excluded auto-generated formatting changes)
### Why
Because the toolsets for old clang-format is out-of-date. This reduces
the development efficiency.
- The NPM package `clang-format` is already in maintenance mode. not
updated since 2 years ago.
- The VSCode extension for clang-format is not maintained for a while,
and a recent Node.js security update made it not working at all in
Windows.
No one in community seems interested in fixing those.
Choose Prettier as it is the most popular TS/JS formatter.
### How to merge
It's easy to break the build:
- Be careful of any new commits on main not included in this PR.
- Be careful that after this PR is merged, other PRs that already passed
CI can merge.
So, make sure there is no new commits before merging this one, and
invalidate js PRs that already passed CI, force them to merge to latest.
475 lines
14 KiB
TypeScript
475 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;
|