* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as fs from 'fs';
import * as ts from 'typescript';
export interface CompileEvent {
createSubEvent?: (eventName: string) => CompileEvent;
createEvent: (eventName: string) => CompileEvent;
startAsyncEvent?: () => void;
start: () => void;
stopAsyncEvent?: () => void;
stop: () => void;
setTotalTime: (time: number) => void;
}
interface Event {
id: string;
name: string;
startTime: number;
endTime: number;
parentEvent: string;
parentId: string;
duration: number;
}
export enum AnalyzeMode {
DEFAULT,
VERBOSE,
FALSE,
TRACE
}
export enum ExternalEventType {
TSC,
ES2ABC
}
let asyncEvents: Map<string, CompileEvent> = new Map();
export function isNeedPerformanceDotting(projectConfig: Object): boolean {
return projectConfig.perf === AnalyzeMode.TRACE || projectConfig.perf === AnalyzeMode.VERBOSE;
}
export function getHookEventFactory(share: Object, pluginName: string, hookName: string): CompileEvent | undefined {
if (typeof share.getHookEventFactory === 'function' && isNeedPerformanceDotting(share.projectConfig)) {
return share.getHookEventFactory(pluginName, hookName);
} else {
return undefined;
}
}
export function createAndStartEvent(eventOrEventFactory: CompileEvent | undefined, eventName: string,
isAsyncEvent = false): CompileEvent | undefined {
if (eventOrEventFactory === undefined || eventOrEventFactory === null) {
return undefined;
}
let event: CompileEvent;
if (isSubEvent(eventOrEventFactory)) {
event = eventOrEventFactory.createSubEvent(eventName);
} else {
event = eventOrEventFactory.createEvent(eventName);
}
if (isAsyncEvent) {
event.startAsyncEvent();
} else {
event.start();
}
return event;
}
function isSubEvent(event: CompileEvent): boolean {
return typeof event.createSubEvent === 'function';
}
export function stopEvent(event: CompileEvent | undefined, isAsyncEvent: boolean = false): void {
if (event === undefined || event === null) {
return;
}
if (isAsyncEvent) {
event.stopAsyncEvent();
} else {
event.stop();
}
}
export function processExternalEvents(projectConfig: Object, eventType: number, externalEventsInfo: Object): void {
if (!isNeedPerformanceDotting(projectConfig)) {
return;
}
const isTsc: boolean = isTscEvents(eventType);
const { parentEvent, filePath } = externalEventsInfo;
let events: Event[] = isTsc ? ts.PerformanceDotting.getEventData() :
(fs.existsSync(filePath) ? JSON.parse(fs.readFileSync(filePath, 'utf-8')) : []);
if (events && events.length) {
events = events.sort((a, b) => a.startTime - b.startTime);
asyncEvents.set(isTsc ? events[0].parentId : events[0].parentEvent, parentEvent);
for (const event of events) {
setTotalTime(event, isTsc);
}
}
asyncEvents.clear();
}
function isTscEvents(eventType: number): boolean {
return eventType === ExternalEventType.TSC;
}
function setTotalTime(event: Event, isTsc: boolean): void {
const parentEvent: CompileEvent | undefined = getCurrentAsyncEvent(isTsc ? event.parentId : event.parentEvent);
if (parentEvent) {
const subEvent: CompileEvent = parentEvent.createSubEvent(event.name);
subEvent.start();
subEvent.stop();
subEvent.setTotalTime(event.duration);
asyncEvents.set(isTsc ? event.id : event.name, subEvent);
}
}
function getCurrentAsyncEvent(eventName): CompileEvent | undefined {
if (asyncEvents && asyncEvents.has(eventName)) {
return asyncEvents.get(eventName);
}
return undefined;
}
export function cleanUpAsyncEvents(): void {
asyncEvents.clear();
}