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}