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}