/* * 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: Unique identifier of the event. id: string; // name: The name of the event. Duplicates exist. 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 = 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(); }