1/*
2 * Copyright (c) 2024 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
16import * as fs from 'fs';
17
18export enum EventList {
19  OBFUSCATION_INITIALIZATION = 'Obfuscation initialization',
20  ALL_FILES_OBFUSCATION = 'All files obfuscation',
21  CREATE_AST = 'Create AST',
22  OBFUSCATE_AST = 'Obfuscate AST',
23  VIRTUAL_CONSTRUCTOR_OBFUSCATION = 'Virtual constructor obfuscation',
24  SHORT_HAND_OBFUSCATION = 'Shorthand obfuscation',
25  REMOVE_CONSOLE = 'Remove console',
26  PROPERTY_OBFUSCATION = 'Property obfuscation',
27  IDENTIFIER_OBFUSCATION = 'Identifier obfuscation',
28  CREATE_CHECKER = 'Create checker',
29  SCOPE_ANALYZE = 'Scope analyze',
30  CREATE_OBFUSCATED_NAMES = 'Create obfuscated names',
31  OBFUSCATE_NODES = 'Obfuscate nodes',
32  FILENAME_OBFUSCATION = 'Filename obfuscation',
33  CREATE_PRINTER = 'Create Printer'
34}
35
36export enum EventIndentation {
37  TWOSPACE = 2,
38  ONESPACE = 1,
39  NOSPACE = 0,
40};
41
42export const eventList = new Map<string, number>([
43  [EventList.OBFUSCATION_INITIALIZATION, EventIndentation.NOSPACE],
44  [EventList.ALL_FILES_OBFUSCATION, EventIndentation.NOSPACE],
45  [EventList.CREATE_AST, EventIndentation.ONESPACE],
46  [EventList.OBFUSCATE_AST, EventIndentation.ONESPACE],
47  [EventList.VIRTUAL_CONSTRUCTOR_OBFUSCATION, EventIndentation.TWOSPACE],
48  [EventList.SHORT_HAND_OBFUSCATION, EventIndentation.TWOSPACE],
49  [EventList.REMOVE_CONSOLE, EventIndentation.TWOSPACE],
50  [EventList.PROPERTY_OBFUSCATION, EventIndentation.TWOSPACE],
51  [EventList.IDENTIFIER_OBFUSCATION, EventIndentation.TWOSPACE],
52  [EventList.CREATE_CHECKER, EventIndentation.TWOSPACE],
53  [EventList.SCOPE_ANALYZE, EventIndentation.TWOSPACE],
54  [EventList.CREATE_OBFUSCATED_NAMES, EventIndentation.TWOSPACE],
55  [EventList.OBFUSCATE_NODES, EventIndentation.TWOSPACE],
56  [EventList.FILENAME_OBFUSCATION, EventIndentation.TWOSPACE],
57  [EventList.CREATE_PRINTER, EventIndentation.ONESPACE],
58]);
59
60export interface TimeAndMemInfo {
61  start: number;
62  duration: number;
63  startMemory: number;
64  endMemory: number;
65  memoryUsage: number;
66}
67
68const MILLISECOND_TO_SECOND = 1000;
69const BYTE_TO_MB = 1024 * 1024;
70const SIG_FIGS = 3;// 有效数字位数
71const INDENT = '    ';
72
73abstract class BasePrinter {
74  protected outputPath: string | undefined;
75
76  protected abstract getCurrentEventData(): string;
77
78  constructor(outputPath: string = "") {
79    this.outputPath = outputPath;
80  }
81
82  setOutputPath(outputPath: string | undefined) {
83    this.outputPath = outputPath;
84  }
85
86  print(message: string): void {
87    if (this.outputPath && this.outputPath !== "") {
88      fs.appendFileSync(`${this.outputPath}`, message + "\n");
89    } else {
90      console.log(message);
91    }
92  }
93
94  outputData(): void {
95    const eventData = this.getCurrentEventData();
96    this.print(eventData);
97  }
98
99  // Only used for ut
100  getOutputPath(): string {
101    return this.outputPath;
102  }
103}
104
105export class TimeTracker extends BasePrinter {
106  private eventStack: Map<string, TimeAndMemInfo> = new Map<string, TimeAndMemInfo>();
107  private filesTimeSum: number = 0;
108  private maxTimeUsage = 0;
109  private maxTimeFile = "";
110  private maxMemoryUsage: number = 0;
111  private maxMemoryFile: string = '';
112
113  startEvent(eventName: string, timeSumPrinter?: TimeSumPrinter, currentFile?: string): void {
114    this.eventStack.set(eventName, {start: Date.now(), duration: 0, startMemory: process.memoryUsage().heapUsed,
115      endMemory: 0, memoryUsage: 0});
116    timeSumPrinter?.addEventDuration(eventName, 0);
117    if (eventName === EventList.CREATE_AST) {
118      this.print(currentFile);
119    }
120  }
121
122  endEvent(eventName: string, timeSumPrinter?: TimeSumPrinter, isFilesPrinter?: boolean): void {
123    if (!this.eventStack.get(eventName)) {
124      throw new Error(`Event "${eventName}" not started`);
125    }
126
127    const eventStartTime = this.eventStack.get(eventName).start;
128    const duration = (Date.now() - eventStartTime)/MILLISECOND_TO_SECOND;
129    const eventEndMemory = process.memoryUsage().heapUsed;
130    const eventStartMemory = this.eventStack.get(eventName).startMemory;
131    const memoryUsage = eventEndMemory - eventStartMemory;
132
133    if (isFilesPrinter) {
134      this.filesTimeSum += duration;
135
136      if (duration > this.maxTimeUsage) {
137        this.maxTimeUsage = duration;
138        this.maxTimeFile = eventName;
139      }
140
141      if (eventStartMemory > this.maxMemoryUsage) {
142        this.maxMemoryUsage = eventStartMemory;
143        this.maxMemoryFile = eventName;
144      }
145
146      if (eventEndMemory > this.maxMemoryUsage) {
147        this.maxMemoryUsage = eventEndMemory;
148        this.maxMemoryFile = eventName;
149      }
150    }
151
152    this.eventStack.get(eventName).duration = duration;
153    this.eventStack.get(eventName).endMemory = eventEndMemory;
154    this.eventStack.get(eventName).memoryUsage = memoryUsage;
155
156    timeSumPrinter?.addEventDuration(eventName, duration);
157
158    if ((eventName === EventList.ALL_FILES_OBFUSCATION)) {
159      this.eventStack.get(eventName).duration = this.filesTimeSum;
160      this.outputData();
161      const maxTimeUsage = this.maxTimeUsage.toFixed(SIG_FIGS);
162      const maxMemoryUsage = (this.maxMemoryUsage/BYTE_TO_MB).toFixed(SIG_FIGS);
163      this.print(`Max time cost: ${this.maxTimeFile}: ${maxTimeUsage}s`)
164      this.print(`Max memory usage: ${this.maxMemoryFile}: ${maxMemoryUsage}MB\n`)
165    }
166
167    if ((eventName === EventList.CREATE_PRINTER)) {
168      this.outputData();
169    }
170  }
171
172  getCurrentEventData(): string {
173    let eventData = "";
174    for (const eventName of this.eventStack.keys()) {
175      let depth = eventList.get(eventName)?? 0;
176      let eventInfo = this.eventStack.get(eventName);
177      const duration = eventInfo.duration;
178      const startMemory = eventInfo.startMemory/BYTE_TO_MB;
179      const endMemory = eventInfo.endMemory/BYTE_TO_MB;
180      const memoryUsage = eventInfo.memoryUsage/BYTE_TO_MB;
181      eventData += this.formatEvent(eventName, duration, startMemory, endMemory, memoryUsage, depth);
182    }
183    return eventData;
184  }
185
186  private formatEvent(eventName: string, duration: number, startMemory: number, endMemory: number,
187    memoryUsage: number, depth: number): string {
188    const indent = INDENT.repeat(depth);
189    const formattedDuration = duration.toFixed(SIG_FIGS) + 's';
190    const formatttedStartMemory = startMemory.toFixed(SIG_FIGS) + 'MB';
191    const formatttedEndMemory  = endMemory.toFixed(SIG_FIGS) + 'MB';
192    const formatttedMemoryUsage  = memoryUsage.toFixed(SIG_FIGS) + 'MB';
193    return `${indent}${eventName}: timeCost:${formattedDuration} startMemory:${formatttedStartMemory} `+
194    `endMemory:${formatttedEndMemory} memoryUsage:${formatttedMemoryUsage}\n`;
195  }
196
197  // Only used for ut
198  getEventStack(): Map<string, TimeAndMemInfo> {
199    return this.eventStack;
200  }
201
202  getFilesTimeSum(): number {
203    return this.filesTimeSum;
204  }
205
206  getMaxTimeUsage(): number {
207    return this.maxTimeUsage;
208  }
209
210  getMaxTimeFile(): string {
211    return this.maxTimeFile;
212  }
213
214  getMaxMemoryUsage(): number {
215    return this.maxMemoryUsage;
216  }
217
218  getMaxMemoryFile(): string {
219    return this.maxMemoryFile;
220  }
221}
222
223export class TimeSumPrinter extends BasePrinter {
224  private eventSum: Map<string, number> = new Map<string, number>();
225  addEventDuration(eventName: string, duration: number): void {
226    const currentValue = this.eventSum.get(eventName) ?? 0;
227    this.eventSum.set(eventName, currentValue + duration);
228  }
229
230  summarizeEventDuration(): void {
231    const eventData = this.getCurrentEventData();
232    this.print(eventData);
233  }
234
235  getCurrentEventData(): string {
236    let eventData = "";
237    for (const eventName of this.eventSum.keys()) {
238      let depth = eventList.get(eventName)?? 0;
239      const duration = this.eventSum.get(eventName);
240      eventData += this.formatEvent(eventName, duration, depth);
241    }
242    return eventData;
243  }
244
245  private formatEvent(eventName: string, duration: number, depth: number): string {
246    const indent = INDENT.repeat(depth);
247    const formattedDuration = duration.toFixed(SIG_FIGS) + 's';
248    return `${indent}${eventName}: ${formattedDuration}\n`;
249  }
250
251  getEventSum(): Map<string, number> {
252    return this.eventSum;
253  }
254}