• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 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 */
15import { ReadonlyESMap, Map } from "./_namespaces/ts";
16export namespace PerformanceDotting {
17    const eventStack: string[] = []; // record the event
18    const timeStack: string[] = []; // sort the relation of Parent-Child time
19    let tempMap = new Map<string, Array<PerformanceData>>; // deal with the template performance dotting
20    let recordMap = new Map<string, Array<PerformanceData>>; // record the performance dotting
21    let timeHooks = require('perf_hooks'); // the method of processing time
22    let count: number = 0;
23    const layerCount = 5; // limit the layer number
24    let advancedSwitch = false; // the advanced switch
25    let traceSwitch = false; // the trace switch
26    const TIME_CONVERSION: number = 1000000; // the conversion of time
27
28    // Accordding to this parameter based on the enumeration sequence of switches set in the hvigor.
29    export enum AnalyzeMode {
30        DEFAULT = 0,
31        VERBOSE = 1,
32        TRACE = 3,
33    }
34
35    interface PerformanceData {
36        startTime: number;
37        endTime: number;
38        duration: number;
39        name: string;
40        parentEvent: string;
41        tier: number;
42        fullPath: string;
43        id: string;
44        parentId: string;
45        fileName: string;
46    }
47
48    export function setPerformanceSwitch(projectConfigPerf: AnalyzeMode): void {
49        if (projectConfigPerf === AnalyzeMode.VERBOSE) {
50            advancedSwitch = true;
51        }
52        if (projectConfigPerf === AnalyzeMode.TRACE) {
53            advancedSwitch = true;
54            traceSwitch = true;
55        }
56    }
57
58    function processSharedStart(eventName: string, fileName: string): void {
59        if (++count > layerCount) {
60            return;
61        }
62
63        const currentEvent = eventName;
64        const currentParentEvent = eventStack.length > 0 ? eventStack[eventStack.length - 1] : '';
65        const currentParentId = timeStack.length > 0 ? timeStack[timeStack.length - 1] : '';
66        const fullPath = eventStack.length > 0 ? `${eventStack.join('-')}-${eventName}` : eventName;
67        let startTime = timeHooks.performance.now();
68        let id = startTime.toString();
69        const recordCurrentData: PerformanceData = {
70            startTime: startTime,
71            endTime: -1,
72            duration: 0,
73            name: currentEvent,
74            parentEvent: currentParentEvent,
75            tier: count,
76            fullPath: fullPath,
77            id: id,
78            parentId: currentParentId,
79            fileName: fileName
80        };
81
82        addOrUpdateEventData(tempMap, eventName, recordCurrentData);
83        eventStack.push(eventName);
84        timeStack.push(id);
85    }
86
87    function processSharedStop(eventName: string): void {
88        if (count-- > layerCount) {
89            return;
90        }
91
92        if (eventStack.length === 0 || eventStack[eventStack.length - 1] !== eventName) {
93            throw new Error(`Event ${eventName} is not the current active event`);
94        }
95
96        const records = tempMap.get(eventName);
97        if (records && records.length > 0) {
98            const lastRecord = records.pop()!;
99            lastRecord.endTime = timeHooks.performance.now();
100            lastRecord.duration = lastRecord.endTime - lastRecord.startTime;
101            addOrUpdateEventData(recordMap, eventName, lastRecord);
102        } else {
103            throw new Error(`No active record found for event ${eventName}`);
104        }
105
106        // remove the current event
107        eventStack.pop();
108        timeStack.pop();
109    }
110
111    export function startAdvanced(eventName: string, fileName: string = ''): void {
112        if (!advancedSwitch) {
113            return;
114        }
115        processSharedStart(eventName, fileName);
116    }
117
118    export function stopAdvanced(eventName: string): void {
119        if (!advancedSwitch) {
120            return;
121        }
122        processSharedStop(eventName);
123    }
124
125    export function start(eventName: string, fileName: string = ''): void {
126        if (!traceSwitch) {
127            return;
128        }
129        processSharedStart(eventName, fileName);
130    }
131
132    function addOrUpdateEventData(map: Map<Array<PerformanceData>>, eventName: string, data: PerformanceData): void {
133        let records = map.get(eventName);
134        if (!records) {
135            records = [];
136            map.set(eventName, records);
137        }
138        records.push(data);
139    }
140
141    export function stop(eventName: string): void {
142        if (!traceSwitch) {
143            return;
144        }
145        processSharedStop(eventName);
146    }
147
148    export function getEventData(): Array<PerformanceData> {
149        const mergedRecordsMap: Map<PerformanceData> = new Map();
150
151        // traverse all records
152        recordMap.forEach((records) => {
153            records.forEach((record) => {
154                // generate unique key
155                const key = `${record.fullPath}-${record.fileName}-${record.parentId}`;
156
157                // check whether the system already exists
158                if (mergedRecordsMap.has(key)) {
159                    const existingRecord = mergedRecordsMap.get(key)!;
160                    // add duration
161                    existingRecord.duration += record.duration;
162                    // update the smaller value of startTime
163                    existingRecord.startTime = Math.min(existingRecord.startTime, record.startTime);
164                    existingRecord.endTime = Math.max(existingRecord.endTime, record.endTime);
165                } else {
166                    // add new record, and remove tier
167                    mergedRecordsMap.set(key, record);
168                }
169            });
170        });
171
172        let mergedRecords: Array<PerformanceData> = Array.from(mapValuesToArray(mergedRecordsMap));
173        mergedRecords = mergedRecords.map(record => ({
174            ...record,
175            duration: record.duration * TIME_CONVERSION
176        }));
177        clearEvent();
178        return mergedRecords;
179    }
180
181    function mapValuesToArray<K, V>(map: ReadonlyESMap<K, V>): V[] {
182        const result: V[] = [];
183        const iterator = map.values();
184
185        let item: IteratorResult<V>;
186        while (!(item = iterator.next()).done) {
187            result.push(item.value);
188        }
189        return result;
190    }
191
192    export function clearEvent(): void {
193        recordMap.clear();
194        tempMap.clear();
195        eventStack.length = 0;
196        timeStack.length = 0;
197        count = 0;
198        advancedSwitch = false;
199        traceSwitch = false;
200    }
201}