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