1/* 2 * Copyright (c) 2022-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16interface Event { 17 name: string, 18 startTime: number, 19 endTime?: number, 20 parentEvent?: string, 21 duration?: number 22} 23 24function formatTime(ms: number): string { 25 const milliseconds = Math.floor(ms % 1000); 26 const seconds = Math.floor((ms / 1000) % 60); 27 const minutes = Math.floor((ms / (1000 * 60)) % 60); 28 const hours = Math.floor(ms / (1000 * 60 * 60)); 29 30 return `${pad(hours, 2)}:${pad(minutes, 2)}:${pad(seconds, 2)}:${pad(milliseconds, 3)}`; 31} 32 33function pad(value: number, length: number): string { 34 return value.toString().padStart(length, '0'); 35} 36 37export class Performance { 38 private static instance: Performance; 39 private events: Map<string, Event>; 40 private scopes: string[]; 41 private shouldSkip: boolean; 42 private totalDuration: number; 43 44 private constructor() { 45 this.events = new Map(); 46 this.scopes = []; 47 this.shouldSkip = true; 48 this.totalDuration = 0; 49 } 50 51 public static getInstance(): Performance { 52 if (!this.instance) { 53 this.instance = new Performance(); 54 } 55 return this.instance; 56 } 57 58 skip(shouldSkip: boolean = true): void { 59 this.shouldSkip = shouldSkip; 60 } 61 62 createEvent(name: string): Event { 63 if (this.shouldSkip) return { name: '', startTime: 0 }; 64 const startTime: number = performance.now(); 65 const newEvent: Event = { name, startTime }; 66 this.events.set(name, newEvent); 67 this.scopes.push(name); 68 return newEvent; 69 } 70 71 stopEvent(name: string, shouldLog: boolean = false): Event { 72 if (this.shouldSkip) return { name: '', startTime: 0 }; 73 if (!this.events.has(name) || this.scopes.length === 0) { 74 throw new Error(`Event ${name} is not created.`); 75 } 76 if (this.scopes[this.scopes.length - 1] !== name) { 77 console.warn(`[PERFORMANCE WARNING] Event ${name} early exit.`); 78 } 79 this.scopes.pop(); 80 81 const event: Event = this.events.get(name)!; 82 const endTime: number = performance.now(); 83 const parentEvent: string = this.scopes[this.scopes.length - 1]; 84 const duration: number = endTime - event.startTime; 85 this.totalDuration += duration; 86 87 if (shouldLog) { 88 console.log( 89 `[PERFORMANCE] name: ${event.name}, parent: ${parentEvent}, duration: ${formatTime(duration)}, total: ${formatTime(this.totalDuration)}` 90 ); 91 } 92 93 return { ...event, endTime, parentEvent, duration }; 94 } 95 96 stopLastEvent(shouldLog: boolean = false): Event { 97 if (this.shouldSkip) return { name: '', startTime: 0 }; 98 if (this.scopes.length === 0) { 99 throw new Error("No last event"); 100 } 101 const name: string = this.scopes.pop()!; 102 if (!this.events.has(name)) { 103 throw new Error(`Event ${name} is not created.`); 104 } 105 106 const event: Event = this.events.get(name)!; 107 const endTime: number = performance.now(); 108 const parentEvent: string = this.scopes[this.scopes.length - 1]; 109 const duration: number = endTime - event.startTime; 110 this.totalDuration += duration; 111 112 if (shouldLog) { 113 console.log( 114 `[PERFORMANCE] name: ${event.name}, parent: ${parentEvent}, duration: ${formatTime(duration)}, total: ${formatTime(this.totalDuration)}` 115 ); 116 } 117 118 return { ...event, endTime, parentEvent, duration }; 119 } 120 121 clearAllEvents(shouldLog: boolean = false): void { 122 if (this.shouldSkip) return; 123 for (let i = 0; i < this.scopes.length; i ++) { 124 this.stopLastEvent(shouldLog); 125 } 126 this.events = new Map(); 127 } 128 129 clearTotalDuration(): void { 130 this.totalDuration = 0; 131 } 132}