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 { Worker } from 'worker_threads'; 17import { 18 getMemoryReport, 19 MemoryReport 20} from './memory_report'; 21import { projectConfig } from '../../../main'; 22import path from 'path'; 23import fs from 'fs'; 24import { ArkObfuscator } from 'arkguard'; 25import * as ts from 'typescript'; 26 27export class MemoryMonitor { 28 private static instance: MemoryMonitor | null = null; 29 private worker: Worker | undefined; 30 private pendingQueries: Map<number, (report: MemoryReport) => void> = new Map(); 31 private requestIdCounter = 0; 32 private filePath = ''; 33 private recordIndexInfo: Map<string, number> = new Map(); 34 35 constructor() { 36 this.worker = undefined; 37 } 38 39 static getInstance(): MemoryMonitor { 40 if (!MemoryMonitor.instance) { 41 MemoryMonitor.instance = new MemoryMonitor(); 42 } 43 return MemoryMonitor.instance; 44 } 45 46 static recordStage(stage: string): RecordInfo { 47 return MemoryMonitor.getInstance().recordStageInner(stage); 48 } 49 50 static stopRecordStage(recordInfo: RecordInfo): void { 51 return MemoryMonitor.getInstance().stopRecordStageInner(recordInfo); 52 } 53 54 getRecordFileName(): string { 55 return this.filePath; 56 } 57 58 start(): void { 59 if (!projectConfig.enableMemoryDotting) { 60 return; 61 } 62 if ( 63 projectConfig.enableMemoryDotting && 64 projectConfig.memoryDottingPath !== undefined && 65 !fs.existsSync(projectConfig.memoryDottingPath) 66 ) { 67 fs.mkdirSync(projectConfig.memoryDottingPath); 68 } else { 69 if (projectConfig.buildPath === undefined) { 70 projectConfig.buildPath = ''; 71 } 72 projectConfig.memoryDottingPath = path.resolve(projectConfig.buildPath, '../', '../', 'dottingfile'); 73 } 74 this.filePath = path.resolve(projectConfig.memoryDottingPath, `memory${Date.now()}.log`); 75 let bufferInterval = 76 (projectConfig.memoryDottingRecordInterval && projectConfig.memoryDottingRecordInterval) || 100; 77 let writeInterval = 78 (projectConfig.memoryDottingWriteFileInterval && projectConfig.memoryDottingWriteFileInterval) || 1000; 79 const workerPath = path.resolve(__dirname, './memory_worker.js'); 80 this.worker = new Worker(workerPath, { 81 workerData: { bufferInterval: bufferInterval, writeInterval: writeInterval, filePath: this.filePath }, 82 }); 83 this.worker.on('error', (err) => { 84 console.error('Worker error:', err); 85 }); 86 this.worker.on('exit', (code) => { 87 if (code !== 0) { 88 console.error(`Worker stopped with exit code ${code}`); 89 } 90 }); 91 92 this.worker.on('message', (data) => { 93 if (data.action === 'memoryReport') { 94 const requestId = data.requestId; 95 const report = data.report; 96 const resolveCallback = this.pendingQueries.get(requestId); 97 if (resolveCallback) { 98 resolveCallback(report); 99 this.pendingQueries.delete(requestId); 100 } 101 } else if (data.action === 'stop_end') { 102 ArkObfuscator.clearMemoryDottingCallBack(); 103 ts.MemoryDotting.clearCallBack(); 104 } 105 }); 106 } 107 108 stop(): void { 109 if (this.worker) { 110 this.worker.postMessage({ action: 'stop' }); 111 } 112 } 113 114 recordStageInner(stage: string): RecordInfo { 115 let recordIndex = this.recordIndexInfo.get(stage); 116 if (recordIndex !== undefined) { 117 recordIndex = recordIndex + 1; 118 this.recordIndexInfo.set(stage, recordIndex); 119 } else { 120 recordIndex = 1; 121 this.recordIndexInfo.set(stage, recordIndex); 122 } 123 if (this.worker) { 124 const memoryUsage = getMemoryReport(stage); 125 this.worker.postMessage({ 126 action: 'recordStage', 127 stage: stage, 128 memoryReport: memoryUsage, 129 recordIndex: recordIndex 130 }); 131 } 132 return { recordStage: stage, recordIndex: recordIndex }; 133 } 134 135 stopRecordStageInner(recordInfo: RecordInfo): void { 136 if (this.worker) { 137 const memoryUsage = getMemoryReport(recordInfo.recordStage); 138 this.worker.postMessage({ action: 'stopRecordStage', stage: recordInfo.recordStage, memoryReport: memoryUsage }); 139 } 140 } 141 142 addMemoryReport(memoryReport: MemoryReport): void { 143 if (this.worker) { 144 this.worker.postMessage({ action: 'addMemoryReport', memoryReport: memoryReport }); 145 } 146 } 147 148 async queryMemoryUsage(stage: string): Promise<MemoryReport> { 149 return new Promise((resolve, reject) => { 150 if (this.worker) { 151 const requestId = ++this.requestIdCounter; 152 this.pendingQueries.set(requestId, resolve); 153 this.worker.postMessage({ action: 'queryMemoryUsage', requestId, stage }); 154 } else { 155 reject(new Error('Worker is not initialized.')); 156 } 157 }); 158 } 159 160 cleanUp(): void { 161 if (this.worker) { 162 this.worker.terminate(); 163 this.worker = undefined; 164 MemoryMonitor.instance = null; 165 } 166 } 167} 168 169export interface RecordInfo { 170 recordStage: string; 171 recordIndex: number; 172} 173 174function setMemoryDottingCallBack(): void { 175 if (projectConfig.enableMemoryDotting) { 176 if (ts.MemoryDotting.setMemoryDottingCallBack !== undefined) { 177 ts.MemoryDotting.setMemoryDottingCallBack((stage: string) => { 178 return MemoryMonitor.recordStage(stage); 179 }, (recordInfo: RecordInfo) => { 180 MemoryMonitor.stopRecordStage(recordInfo); 181 }); 182 } 183 if (ArkObfuscator.setMemoryDottingCallBack !== undefined) { 184 ArkObfuscator.setMemoryDottingCallBack((stage: string) => { 185 return MemoryMonitor.recordStage(stage); 186 }, (recordInfo: RecordInfo) => { 187 MemoryMonitor.stopRecordStage(recordInfo); 188 }); 189 } 190 } 191} 192 193interface MemoryMonitorLifecycle { 194 name: string; 195 buildStart: { 196 order: 'pre'; 197 handler(): void; 198 }; 199 buildEnd: { 200 order: 'post'; 201 handler(): void; 202 }; 203} 204 205export function memoryMonitor(): MemoryMonitorLifecycle { 206 return { 207 name: 'memoryMonitor', 208 buildStart: { 209 order: 'pre', 210 handler(): void { 211 const memoryMonitorInstance: MemoryMonitor = MemoryMonitor.getInstance(); 212 memoryMonitorInstance.start(); 213 setMemoryDottingCallBack(); 214 }, 215 }, 216 buildEnd: { 217 order: 'post', 218 handler(): void { 219 const memoryMonitorInstance: MemoryMonitor = MemoryMonitor.getInstance(); 220 memoryMonitorInstance.stop(); 221 }, 222 }, 223 }; 224} 225