onnxruntime
475 строк · 14.5 Кб
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4import { Env } from 'onnxruntime-common';
5
6import { WebGLContext } from './backends/webgl/webgl-context';
7
8export declare namespace Logger {
9export interface SeverityTypeMap {
10verbose: 'v';
11info: 'i';
12warning: 'w';
13error: 'e';
14fatal: 'f';
15}
16
17export type Severity = keyof SeverityTypeMap;
18
19export type Provider = 'none' | 'console';
20
21/**
22* Logging config that used to control the behavior of logger
23*/
24export interface Config {
25/**
26* Specify the logging provider. 'console' by default
27*/
28provider?: Provider;
29/**
30* Specify the minimal logger serverity. 'warning' by default
31*/
32minimalSeverity?: Logger.Severity;
33/**
34* Whether to output date time in log. true by default
35*/
36logDateTime?: boolean;
37/**
38* Whether to output source information (Not yet supported). false by default
39*/
40logSourceLocation?: boolean;
41}
42
43export interface CategorizedLogger {
44verbose(content: string): void;
45info(content: string): void;
46warning(content: string): void;
47error(content: string): void;
48fatal(content: string): void;
49}
50}
51
52// eslint-disable-next-line @typescript-eslint/no-redeclare
53export interface Logger {
54(category: string): Logger.CategorizedLogger;
55
56verbose(content: string): void;
57verbose(category: string, content: string): void;
58info(content: string): void;
59info(category: string, content: string): void;
60warning(content: string): void;
61warning(category: string, content: string): void;
62error(content: string): void;
63error(category: string, content: string): void;
64fatal(content: string): void;
65fatal(category: string, content: string): void;
66
67/**
68* Reset the logger configuration.
69* @param config specify an optional default config
70*/
71reset(config?: Logger.Config): void;
72/**
73* Set the logger's behavior on the given category
74* @param category specify a category string. If '*' is specified, all previous configuration will be overwritten. If
75* '' is specified, the default behavior will be updated.
76* @param config the config object to indicate the logger's behavior
77*/
78set(category: string, config: Logger.Config): void;
79
80/**
81* Set the logger's behavior from ort-common env
82* @param env the env used to set logger. Currently only setting loglevel is supported through Env.
83*/
84setWithEnv(env: Env): void;
85}
86
87interface LoggerProvider {
88log(severity: Logger.Severity, content: string, category?: string): void;
89}
90class NoOpLoggerProvider implements LoggerProvider {
91log(_severity: Logger.Severity, _content: string, _category?: string) {
92// do nothing
93}
94}
95class ConsoleLoggerProvider implements LoggerProvider {
96log(severity: Logger.Severity, content: string, category?: string) {
97// eslint-disable-next-line no-console
98console.log(`${this.color(severity)} ${category ? '\x1b[35m' + category + '\x1b[0m ' : ''}${content}`);
99}
100
101private color(severity: Logger.Severity) {
102switch (severity) {
103case 'verbose':
104return '\x1b[34;40mv\x1b[0m';
105case 'info':
106return '\x1b[32mi\x1b[0m';
107case 'warning':
108return '\x1b[30;43mw\x1b[0m';
109case 'error':
110return '\x1b[31;40me\x1b[0m';
111case 'fatal':
112return '\x1b[101mf\x1b[0m';
113default:
114throw new Error(`unsupported severity: ${severity}`);
115}
116}
117}
118
119const SEVERITY_VALUE = {
120verbose: 1000,
121info: 2000,
122warning: 4000,
123error: 5000,
124fatal: 6000,
125};
126
127const LOGGER_PROVIDER_MAP: { readonly [provider: string]: Readonly<LoggerProvider> } = {
128['none']: new NoOpLoggerProvider(),
129['console']: new ConsoleLoggerProvider(),
130};
131const LOGGER_DEFAULT_CONFIG = {
132provider: 'console',
133minimalSeverity: 'warning',
134logDateTime: true,
135logSourceLocation: false,
136};
137let LOGGER_CONFIG_MAP: { [category: string]: Readonly<Required<Logger.Config>> } = {
138['']: LOGGER_DEFAULT_CONFIG as Required<Logger.Config>,
139};
140
141function log(category: string): Logger.CategorizedLogger;
142function log(severity: Logger.Severity, content: string): void;
143function log(severity: Logger.Severity, category: string, content: string): void;
144function log(severity: Logger.Severity, arg1: string, arg2?: string): void;
145function log(
146arg0: string | Logger.Severity,
147arg1?: string,
148arg2?: string | number,
149arg3?: number,
150): Logger.CategorizedLogger | void {
151if (arg1 === undefined) {
152// log(category: string): Logger.CategorizedLogger;
153return createCategorizedLogger(arg0);
154} else if (arg2 === undefined) {
155// log(severity, content);
156logInternal(arg0 as Logger.Severity, arg1, 1);
157} else if (typeof arg2 === 'number' && arg3 === undefined) {
158// log(severity, content, stack)
159logInternal(arg0 as Logger.Severity, arg1, arg2);
160} else if (typeof arg2 === 'string' && arg3 === undefined) {
161// log(severity, category, content)
162logInternal(arg0 as Logger.Severity, arg2, 1, arg1);
163} else if (typeof arg2 === 'string' && typeof arg3 === 'number') {
164// log(severity, category, content, stack)
165logInternal(arg0 as Logger.Severity, arg2, arg3, arg1);
166} else {
167throw new TypeError('input is valid');
168}
169}
170
171function createCategorizedLogger(category: string): Logger.CategorizedLogger {
172return {
173verbose: log.verbose.bind(null, category),
174info: log.info.bind(null, category),
175warning: log.warning.bind(null, category),
176error: log.error.bind(null, category),
177fatal: log.fatal.bind(null, category),
178};
179}
180
181// NOTE: argument 'category' is put the last parameter beacause typescript
182// doesn't allow optional argument put in front of required argument. This
183// order is different from a usual logging API.
184function logInternal(severity: Logger.Severity, content: string, _stack: number, category?: string) {
185const config = LOGGER_CONFIG_MAP[category || ''] || LOGGER_CONFIG_MAP[''];
186if (SEVERITY_VALUE[severity] < SEVERITY_VALUE[config.minimalSeverity]) {
187return;
188}
189
190if (config.logDateTime) {
191content = `${new Date().toISOString()}|${content}`;
192}
193
194if (config.logSourceLocation) {
195// TODO: calculate source location from 'stack'
196}
197
198LOGGER_PROVIDER_MAP[config.provider].log(severity, content, category);
199}
200
201// eslint-disable-next-line @typescript-eslint/no-namespace
202namespace log {
203export function verbose(content: string): void;
204export function verbose(category: string, content: string): void;
205export function verbose(arg0: string, arg1?: string) {
206log('verbose', arg0, arg1);
207}
208export function info(content: string): void;
209export function info(category: string, content: string): void;
210export function info(arg0: string, arg1?: string) {
211log('info', arg0, arg1);
212}
213export function warning(content: string): void;
214export function warning(category: string, content: string): void;
215export function warning(arg0: string, arg1?: string) {
216log('warning', arg0, arg1);
217}
218export function error(content: string): void;
219export function error(category: string, content: string): void;
220export function error(arg0: string, arg1?: string) {
221log('error', arg0, arg1);
222}
223export function fatal(content: string): void;
224export function fatal(category: string, content: string): void;
225export function fatal(arg0: string, arg1?: string) {
226log('fatal', arg0, arg1);
227}
228
229export function reset(config?: Logger.Config): void {
230LOGGER_CONFIG_MAP = {};
231set('', config || {});
232}
233export function set(category: string, config: Logger.Config): void {
234if (category === '*') {
235reset(config);
236} else {
237const previousConfig = LOGGER_CONFIG_MAP[category] || LOGGER_DEFAULT_CONFIG;
238LOGGER_CONFIG_MAP[category] = {
239provider: config.provider || previousConfig.provider,
240minimalSeverity: config.minimalSeverity || previousConfig.minimalSeverity,
241logDateTime: config.logDateTime === undefined ? previousConfig.logDateTime : config.logDateTime,
242logSourceLocation:
243config.logSourceLocation === undefined ? previousConfig.logSourceLocation : config.logSourceLocation,
244};
245}
246
247// TODO: we want to support wildcard or regex?
248}
249
250export function setWithEnv(env: Env): void {
251const config: Logger.Config = {};
252if (env.logLevel) {
253config.minimalSeverity = env.logLevel as Logger.Severity;
254}
255set('', config);
256}
257}
258
259// eslint-disable-next-line @typescript-eslint/no-redeclare, @typescript-eslint/naming-convention
260export const Logger: Logger = log;
261
262export declare namespace Profiler {
263export interface Config {
264maxNumberEvents?: number;
265flushBatchSize?: number;
266flushIntervalInMilliseconds?: number;
267}
268
269export type EventCategory = 'session' | 'node' | 'op' | 'backend';
270
271export interface Event {
272end(): void | Promise<void>;
273}
274}
275// TODO
276// class WebGLEvent implements Profiler.Event {}
277
278class Event implements Profiler.Event {
279constructor(
280public category: Profiler.EventCategory,
281public name: string,
282public startTime: number,
283private endCallback: (e: Event) => void | Promise<void>,
284public timer?: WebGLQuery,
285public ctx?: WebGLContext,
286) {}
287
288async end() {
289return this.endCallback(this);
290}
291
292async checkTimer(): Promise<number> {
293if (this.ctx === undefined || this.timer === undefined) {
294throw new Error('No webgl timer found');
295} else {
296this.ctx.endTimer();
297return this.ctx.waitForQueryAndGetTime(this.timer);
298}
299}
300}
301
302class EventRecord {
303constructor(
304public category: Profiler.EventCategory,
305public name: string,
306public startTime: number,
307public endTime: number,
308) {}
309}
310
311export class Profiler {
312static create(config?: Profiler.Config): Profiler {
313if (config === undefined) {
314return new this();
315}
316return new this(config.maxNumberEvents, config.flushBatchSize, config.flushIntervalInMilliseconds);
317}
318
319private constructor(maxNumberEvents?: number, flushBatchSize?: number, flushIntervalInMilliseconds?: number) {
320this._started = false;
321this._maxNumberEvents = maxNumberEvents === undefined ? 10000 : maxNumberEvents;
322this._flushBatchSize = flushBatchSize === undefined ? 10 : flushBatchSize;
323this._flushIntervalInMilliseconds = flushIntervalInMilliseconds === undefined ? 5000 : flushIntervalInMilliseconds;
324}
325
326// start profiling
327start() {
328this._started = true;
329this._timingEvents = [];
330this._flushTime = now();
331this._flushPointer = 0;
332}
333
334// stop profiling
335stop() {
336this._started = false;
337for (; this._flushPointer < this._timingEvents.length; this._flushPointer++) {
338this.logOneEvent(this._timingEvents[this._flushPointer]);
339}
340}
341
342// create an event scope for the specific function
343event<T>(category: Profiler.EventCategory, name: string, func: () => T, ctx?: WebGLContext): T;
344event<T>(category: Profiler.EventCategory, name: string, func: () => Promise<T>, ctx?: WebGLContext): Promise<T>;
345
346event<T>(
347category: Profiler.EventCategory,
348name: string,
349func: () => T | Promise<T>,
350ctx?: WebGLContext,
351): T | Promise<T> {
352const event = this._started ? this.begin(category, name, ctx) : undefined;
353let isPromise = false;
354
355const res = func();
356
357// we consider a then-able object is a promise
358if (res && typeof (res as Promise<T>).then === 'function') {
359isPromise = true;
360return new Promise<T>((resolve, reject) => {
361(res as Promise<T>).then(
362async (value) => {
363// fulfilled
364if (event) {
365await event.end();
366}
367resolve(value);
368},
369async (reason) => {
370// rejected
371if (event) {
372await event.end();
373}
374reject(reason);
375},
376);
377});
378}
379if (!isPromise && event) {
380const eventRes = event.end();
381if (eventRes && typeof eventRes.then === 'function') {
382return new Promise<T>((resolve, reject) => {
383eventRes.then(
384() => {
385// fulfilled
386resolve(res);
387},
388(reason) => {
389// rejected
390reject(reason);
391},
392);
393});
394}
395}
396return res;
397}
398
399// begin an event
400begin(category: Profiler.EventCategory, name: string, ctx?: WebGLContext): Event {
401if (!this._started) {
402throw new Error('profiler is not started yet');
403}
404if (ctx === undefined) {
405const startTime = now();
406this.flush(startTime);
407return new Event(category, name, startTime, (e) => this.endSync(e));
408} else {
409const timer: WebGLQuery = ctx.beginTimer();
410return new Event(category, name, 0, async (e) => this.end(e), timer, ctx);
411}
412}
413
414// end the specific event
415private async end(event: Event): Promise<void> {
416const endTime: number = await event.checkTimer();
417if (this._timingEvents.length < this._maxNumberEvents) {
418this._timingEvents.push(new EventRecord(event.category, event.name, event.startTime, endTime));
419this.flush(endTime);
420}
421}
422
423private endSync(event: Event): void {
424const endTime: number = now();
425if (this._timingEvents.length < this._maxNumberEvents) {
426this._timingEvents.push(new EventRecord(event.category, event.name, event.startTime, endTime));
427this.flush(endTime);
428}
429}
430
431private logOneEvent(event: EventRecord) {
432Logger.verbose(
433`Profiler.${event.category}`,
434`${(event.endTime - event.startTime).toFixed(2)}ms on event '${event.name}' at ${event.endTime.toFixed(2)}`,
435);
436}
437
438private flush(currentTime: number) {
439if (
440this._timingEvents.length - this._flushPointer >= this._flushBatchSize ||
441currentTime - this._flushTime >= this._flushIntervalInMilliseconds
442) {
443// should flush when either batch size accumlated or interval elepsed
444
445for (
446const previousPointer = this._flushPointer;
447this._flushPointer < previousPointer + this._flushBatchSize && this._flushPointer < this._timingEvents.length;
448this._flushPointer++
449) {
450this.logOneEvent(this._timingEvents[this._flushPointer]);
451}
452
453this._flushTime = now();
454}
455}
456
457get started() {
458return this._started;
459}
460private _started = false;
461private _timingEvents: EventRecord[];
462
463private readonly _maxNumberEvents: number;
464
465private readonly _flushBatchSize: number;
466private readonly _flushIntervalInMilliseconds: number;
467
468private _flushTime: number;
469private _flushPointer = 0;
470}
471
472/**
473* returns a number to represent the current timestamp in a resolution as high as possible.
474*/
475export const now = typeof performance !== 'undefined' && performance.now ? () => performance.now() : Date.now;
476