• 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 { 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