• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}