• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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';
17import type { IOptions } from '../configs/IOptions';
18import { performancePrinter, performanceTimeAndMemPrinter } from '../ArkObfuscator';
19import type { IPrinterOption } from '../configs/INameObfuscationOption';
20import { eventListTimeAndMemValues } from './PrinterTimeAndMemUtils';
21
22export enum EventList {
23  OBFUSCATION_INITIALIZATION = 'Obfuscation initialization',
24  SCAN_SYSTEMAPI = 'Scan system api',
25  SCAN_SOURCEFILES = 'Scan source files',
26  ALL_FILES_OBFUSCATION = 'All files obfuscation',
27  OBFUSCATE = 'Obfuscate',
28  CREATE_AST = 'Create AST',
29  OBFUSCATE_AST = 'Obfuscate AST',
30  VIRTUAL_CONSTRUCTOR_OBFUSCATION = 'Virtual constructor obfuscation',
31  SHORT_HAND_OBFUSCATION = 'Shorthand obfuscation',
32  REMOVE_CONSOLE = 'Remove console',
33  PROPERTY_OBFUSCATION = 'Property obfuscation',
34  IDENTIFIER_OBFUSCATION = 'Identifier obfuscation',
35  CREATE_CHECKER = 'Create checker',
36  CREATE_PROGRAM = 'Create program',
37  GET_CHECKER = 'Get checker',
38  SCOPE_ANALYZE = 'Scope analyze',
39  CREATE_OBFUSCATED_NAMES = 'Create obfuscated names',
40  OBFUSCATE_NODES = 'Obfuscate nodes',
41  FILENAME_OBFUSCATION = 'Filename obfuscation',
42  CREATE_PRINTER = 'Create Printer',
43  GET_SOURCEMAP_GENERATOR = 'Get sourcemap generator',
44  SOURCEMAP_MERGE = 'Sourcemap merge',
45  CREATE_NAMECACHE = 'Create namecache',
46  WRITE_FILE = 'Write file'
47}
48
49export enum EventIndentation {
50  FOURSPACE = 4,
51  THREESPACE = 3,
52  TWOSPACE = 2,
53  ONESPACE = 1,
54  NOSPACE = 0,
55};
56
57export const eventList = new Map<string, number>([
58  [EventList.OBFUSCATION_INITIALIZATION, EventIndentation.ONESPACE],
59  [EventList.SCAN_SYSTEMAPI, EventIndentation.TWOSPACE],
60  [EventList.SCAN_SOURCEFILES, EventIndentation.ONESPACE],
61  [EventList.ALL_FILES_OBFUSCATION, EventIndentation.ONESPACE],
62  [EventList.OBFUSCATE, EventIndentation.ONESPACE],
63  [EventList.CREATE_AST, EventIndentation.TWOSPACE],
64  [EventList.OBFUSCATE_AST, EventIndentation.TWOSPACE],
65  [EventList.VIRTUAL_CONSTRUCTOR_OBFUSCATION, EventIndentation.THREESPACE],
66  [EventList.SHORT_HAND_OBFUSCATION, EventIndentation.THREESPACE],
67  [EventList.REMOVE_CONSOLE, EventIndentation.THREESPACE],
68  [EventList.PROPERTY_OBFUSCATION, EventIndentation.THREESPACE],
69  [EventList.IDENTIFIER_OBFUSCATION, EventIndentation.THREESPACE],
70  [EventList.CREATE_CHECKER, EventIndentation.THREESPACE],
71  [EventList.CREATE_PROGRAM, EventIndentation.FOURSPACE],
72  [EventList.GET_CHECKER, EventIndentation.FOURSPACE],
73  [EventList.SCOPE_ANALYZE, EventIndentation.THREESPACE],
74  [EventList.CREATE_OBFUSCATED_NAMES, EventIndentation.THREESPACE],
75  [EventList.OBFUSCATE_NODES, EventIndentation.THREESPACE],
76  [EventList.FILENAME_OBFUSCATION, EventIndentation.THREESPACE],
77  [EventList.CREATE_PRINTER, EventIndentation.TWOSPACE],
78  [EventList.GET_SOURCEMAP_GENERATOR, EventIndentation.TWOSPACE],
79  [EventList.SOURCEMAP_MERGE, EventIndentation.TWOSPACE],
80  [EventList.CREATE_NAMECACHE, EventIndentation.TWOSPACE],
81  [EventList.WRITE_FILE, EventIndentation.ONESPACE]
82]);
83
84export interface TimeAndMemInfo {
85  start: number;
86  duration: number;
87  startMemory: number;
88  endMemory: number;
89  memoryUsage: number;
90  filePath?: string;
91}
92
93const eventListValues = new Set<string>(Object.values(EventList));
94
95const MILLISECOND_TO_SECOND = 1000;
96const BYTE_TO_MB = 1024 * 1024;
97const SIG_FIGS = 3; // Significant figures
98const INDENT = '    ';
99const DEFAULT_DEPTH = 2; // Default indent length
100
101abstract class BasePrinter {
102  protected outputPath: string | undefined;
103
104  protected abstract getCurrentEventData(): string;
105
106  protected enablePrinter = true;
107
108  disablePrinter(): void {
109    this.enablePrinter = false;
110  }
111
112  /**
113   * Only for ut
114   */
115  isEnabled(): boolean {
116    return this.enablePrinter;
117  }
118
119  constructor(outputPath: string = '') {
120    this.outputPath = outputPath;
121  }
122
123  setOutputPath(outputPath: string | undefined): void {
124    this.outputPath = outputPath;
125  }
126
127  print(message: string): void {
128    if (this.outputPath && this.outputPath !== '') {
129      fs.appendFileSync(`${this.outputPath}`, message + '\n');
130    } else {
131      console.log(message);
132    }
133  }
134
135  outputData(): void {
136    const eventData = this.getCurrentEventData();
137    this.print(eventData);
138  }
139
140  // Only used for ut
141  getOutputPath(): string {
142    return this.outputPath;
143  }
144}
145
146export class TimeTracker extends BasePrinter {
147  private eventStack: Map<string, TimeAndMemInfo> = new Map<string, TimeAndMemInfo>();
148  private filesTimeSum: number = 0;
149  private maxTimeUsage = 0;
150  private maxTimeFile = '';
151  private maxMemoryUsage: number = 0;
152  private maxMemoryFile: string = '';
153
154  startEvent(eventName: string, timeSumPrinter?: TimeSumPrinter, currentFile?: string): void {
155    this.eventStack.set(eventName, {start: Date.now(), duration: 0, startMemory: process.memoryUsage().heapUsed,
156      endMemory: 0, memoryUsage: 0, filePath: currentFile});
157    timeSumPrinter?.addEventDuration(eventName, 0);
158  }
159
160  endEvent(eventName: string, timeSumPrinter?: TimeSumPrinter, isFilesPrinter?: boolean, triggerSingleFilePrinter?: boolean): void {
161    if (!this.eventStack.get(eventName)) {
162      throw new Error(`Event "${eventName}" not started`);
163    }
164
165    const eventStartTime = this.eventStack.get(eventName).start;
166    const duration = (Date.now() - eventStartTime) / MILLISECOND_TO_SECOND;
167    const eventEndMemory = process.memoryUsage().heapUsed;
168    const eventStartMemory = this.eventStack.get(eventName).startMemory;
169    const memoryUsage = eventEndMemory - eventStartMemory;
170
171    if (isFilesPrinter) {
172      this.filesTimeSum += duration;
173
174      if (duration > this.maxTimeUsage) {
175        this.maxTimeUsage = duration;
176        this.maxTimeFile = eventName;
177      }
178
179      if (eventStartMemory > this.maxMemoryUsage) {
180        this.maxMemoryUsage = eventStartMemory;
181        this.maxMemoryFile = eventName;
182      }
183
184      if (eventEndMemory > this.maxMemoryUsage) {
185        this.maxMemoryUsage = eventEndMemory;
186        this.maxMemoryFile = eventName;
187      }
188    }
189
190    this.eventStack.get(eventName).duration = duration;
191    this.eventStack.get(eventName).endMemory = eventEndMemory;
192    this.eventStack.get(eventName).memoryUsage = memoryUsage;
193
194    timeSumPrinter?.addEventDuration(eventName, duration);
195
196    // Output data of singleFilePrinter if it is enabled and triggered
197    if (triggerSingleFilePrinter && this.enablePrinter) {
198      this.outputData();
199    }
200
201    // Output data of filesPrinter if it is enabled and triggered
202    if ((eventName === EventList.ALL_FILES_OBFUSCATION)) {
203      this.eventStack.get(eventName).duration = this.filesTimeSum;
204      if (this.enablePrinter) {
205        this.outputData();
206      }
207      const totalTimeUsage = this.getTotalTime();
208      const maxTimeUsage = this.maxTimeUsage.toFixed(SIG_FIGS);
209      const maxMemoryUsage = (this.maxMemoryUsage / BYTE_TO_MB).toFixed(SIG_FIGS);
210      this.print(`Obfuscation time cost: ${totalTimeUsage} s`);
211      this.print(`Max time cost of single file: ${this.maxTimeFile}: ${maxTimeUsage} s`);
212      this.print(`Max memory usage of single file: ${this.maxMemoryFile}: ${maxMemoryUsage}MB\n`);
213    }
214  }
215
216  getCurrentEventData(): string {
217    let eventData = '';
218    for (const eventName of this.eventStack.keys()) {
219      if (eventName === EventList.OBFUSCATION_INITIALIZATION) {
220        const totalTimeUsage = this.getTotalTime();
221        eventData += `Obfuscation time cost: ${totalTimeUsage} s\n`;
222      }
223      let depth = eventList.get(eventName) ?? DEFAULT_DEPTH;
224      let eventInfo = this.eventStack.get(eventName);
225      const duration = eventInfo.duration;
226      const startMemory = eventInfo.startMemory / BYTE_TO_MB;
227      const endMemory = eventInfo.endMemory / BYTE_TO_MB;
228      const memoryUsage = eventInfo.memoryUsage / BYTE_TO_MB;
229      if (eventInfo.filePath) {
230        eventData += eventInfo.filePath + `\n`;
231      }
232      eventData += this.formatEvent(eventName, duration, startMemory, endMemory, memoryUsage, depth);
233    }
234    return eventData;
235  }
236
237  private formatEvent(eventName: string, duration: number, startMemory: number, endMemory: number,
238    memoryUsage: number, depth: number): string {
239    const indent = INDENT.repeat(depth);
240    const formattedDuration = duration.toFixed(SIG_FIGS) + ' s';
241    const formatttedStartMemory = startMemory.toFixed(SIG_FIGS) + 'MB';
242    const formatttedEndMemory = endMemory.toFixed(SIG_FIGS) + 'MB';
243    const formatttedMemoryUsage = memoryUsage.toFixed(SIG_FIGS) + 'MB';
244    return `${indent}${eventName}: timeCost:${formattedDuration} startMemory:${formatttedStartMemory} ` +
245    `endMemory:${formatttedEndMemory} memoryUsage:${formatttedMemoryUsage}\n`;
246  }
247
248  private getTotalTime(): string {
249    let totalTime = (this.eventStack.get(EventList.OBFUSCATION_INITIALIZATION)?.duration ?? 0) +
250    (this.eventStack.get(EventList.SCAN_SOURCEFILES)?.duration ?? 0) +
251    (this.eventStack.get(EventList.ALL_FILES_OBFUSCATION)?.duration ?? 0);
252    return totalTime.toFixed(SIG_FIGS);
253  }
254
255  // Only used for ut
256  getEventStack(): Map<string, TimeAndMemInfo> {
257    return this.eventStack;
258  }
259
260  getFilesTimeSum(): number {
261    return this.filesTimeSum;
262  }
263
264  getMaxTimeUsage(): number {
265    return this.maxTimeUsage;
266  }
267
268  getMaxTimeFile(): string {
269    return this.maxTimeFile;
270  }
271
272  getMaxMemoryUsage(): number {
273    return this.maxMemoryUsage;
274  }
275
276  getMaxMemoryFile(): string {
277    return this.maxMemoryFile;
278  }
279}
280
281export class TimeSumPrinter extends BasePrinter {
282  private eventSum: Map<string, number> = new Map<string, number>();
283
284  addEventDuration(eventName: string, duration: number): void {
285    const currentValue = this.eventSum.get(eventName) ?? 0;
286    this.eventSum.set(eventName, currentValue + duration);
287  }
288
289  summarizeEventDuration(): void {
290    if (this.enablePrinter) {
291      const eventData = this.getCurrentEventData();
292      this.print(eventData);
293    }
294  }
295
296  getCurrentEventData(): string {
297    let eventData = '';
298    for (const eventName of this.eventSum.keys()) {
299      let depth = eventList.get(eventName) ?? 0;
300      const duration = this.eventSum.get(eventName);
301      eventData += this.formatEvent(eventName, duration, depth);
302    }
303    return eventData;
304  }
305
306  private formatEvent(eventName: string, duration: number, depth: number): string {
307    const indent = INDENT.repeat(depth);
308    const formattedDuration = duration.toFixed(SIG_FIGS) + ' s';
309    return `${indent}${eventName}: ${formattedDuration}\n`;
310  }
311
312  getEventSum(): Map<string, number> {
313    return this.eventSum;
314  }
315}
316
317/**
318 * Initialize performance printer
319 */
320export function initPerformancePrinter(mCustomProfiles: IOptions): void {
321  const printerConfig: IPrinterOption = mCustomProfiles.mPerformancePrinter;
322
323  // If no performance printer configuration is provided, disable the printer and return.
324  if (!printerConfig) {
325    blockPrinter();
326    return;
327  }
328
329  // Disable performance printer if no specific printer types (files, single file, or summary) are enabled.
330  const isPrinterDisabled = !(
331    printerConfig.mFilesPrinter ||
332    printerConfig.mSingleFilePrinter ||
333    printerConfig.mSumPrinter
334  );
335
336  if (isPrinterDisabled) {
337    blockPrinter();
338    return;
339  }
340
341  const outputPath: string = printerConfig.mOutputPath;
342
343  // Helper function to configure or disable a printer.
344  const configurePrinter = (printer: TimeTracker | TimeSumPrinter, isEnabled: boolean): void => {
345    if (!isEnabled) {
346      printer?.disablePrinter();
347      return;
348    }
349    printer?.setOutputPath(outputPath);
350  };
351
352  // Setup the individual printers based on configuration.
353  configurePrinter(performancePrinter.filesPrinter, printerConfig.mFilesPrinter);
354  configurePrinter(performancePrinter.singleFilePrinter, printerConfig.mSingleFilePrinter);
355  configurePrinter(performancePrinter.timeSumPrinter, printerConfig.mSumPrinter);
356}
357
358/**
359 * Disable performance printer
360 */
361export function blockPrinter(): void {
362  performancePrinter.filesPrinter = undefined;
363  performancePrinter.singleFilePrinter = undefined;
364  performancePrinter.timeSumPrinter = undefined;
365}
366
367/**
368 * Start recording singleFilePrinter event
369 */
370export function startSingleFileEvent(eventName: string, timeSumPrinter?: TimeSumPrinter, currentFile?: string): void {
371  // The second conditional statement ensures that when the input eventName is a path, the current statement is executed
372  if (eventListValues.has(eventName) || !eventListTimeAndMemValues.has(eventName)) {
373    performancePrinter.singleFilePrinter?.startEvent(eventName, timeSumPrinter, currentFile);
374  }
375  performanceTimeAndMemPrinter.singleFilePrinter?.startEvent(eventName);
376}
377
378/**
379 * End recording singleFilePrinter event
380 */
381export function endSingleFileEvent(
382  eventName: string,
383  timeSumPrinter?: TimeSumPrinter,
384  isFilesPrinter?: boolean,
385  triggerSingleFilePrinter?: boolean,
386): void {
387  if (eventListValues.has(eventName) || !eventListTimeAndMemValues.has(eventName)) {
388    performancePrinter.singleFilePrinter?.endEvent(eventName, timeSumPrinter, isFilesPrinter, triggerSingleFilePrinter);
389  }
390  performanceTimeAndMemPrinter.singleFilePrinter?.endEvent(eventName);
391}
392
393/**
394 * Start recording filesPrinter event
395 */
396export function startFilesEvent(eventName: string, timeSumPrinter?: TimeSumPrinter, currentFile?: string): void {
397  if (eventListValues.has(eventName) || !eventListTimeAndMemValues.has(eventName)) {
398    performancePrinter.filesPrinter?.startEvent(eventName, timeSumPrinter, currentFile);
399  }
400  performanceTimeAndMemPrinter.filesPrinter?.startEvent(eventName);
401}
402
403/**
404 * End recording filesPrinter event
405 */
406export function endFilesEvent(
407  eventName: string,
408  timeSumPrinter?: TimeSumPrinter,
409  isFilesPrinter?: boolean,
410  triggerSingleFilePrinter?: boolean,
411): void {
412  if (eventListValues.has(eventName) || !eventListTimeAndMemValues.has(eventName)) {
413    performancePrinter.filesPrinter?.endEvent(eventName, timeSumPrinter, isFilesPrinter, triggerSingleFilePrinter);
414  }
415  performanceTimeAndMemPrinter.filesPrinter?.endEvent(eventName);
416}
417
418/**
419 * Print input info for timeSumPrinter
420 */
421export function printTimeSumInfo(info: string): void {
422  performancePrinter.timeSumPrinter?.print(info);
423}
424
425/**
426 * Print data of timeSumPrinter
427 */
428export function printTimeSumData(): void {
429  performancePrinter.timeSumPrinter?.summarizeEventDuration();
430}