1/*
2 * Copyright (C) 2022 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 */
15import { SpSystemTrace } from '../SpSystemTrace';
16import { TraceRow } from '../trace/base/TraceRow';
17import { renders } from '../../database/ui-worker/ProcedureWorker';
18import { type EmptyRender } from '../../database/ui-worker/cpu/ProcedureWorkerCPU';
19import { type HeapTimelineRender, HeapTimelineStruct } from '../../database/ui-worker/ProcedureWorkerHeapTimeline';
20import { HeapDataInterface, type ParseListener } from '../../../js-heap/HeapDataInterface';
21import { LoadDatabase } from '../../../js-heap/LoadDatabase';
22import { type FileInfo } from '../../../js-heap/model/UiStruct';
23import { type HeapSnapshotRender, HeapSnapshotStruct } from '../../database/ui-worker/ProcedureWorkerHeapSnapshot';
24import { Utils } from '../trace/base/Utils';
25import { type JsCpuProfilerChartFrame } from '../../bean/JsStruct';
26import { type JsCpuProfilerRender, JsCpuProfilerStruct } from '../../database/ui-worker/ProcedureWorkerCpuProfiler';
27import { ns2s } from '../../database/ui-worker/ProcedureWorkerCommon';
28import { cpuProfilerDataSender } from '../../database/data-trafic/ArkTsSender';
29import { queryJsCpuProfilerConfig, queryJsCpuProfilerData } from '../../database/sql/Cpu.sql';
30import { queryJsMemoryData } from '../../database/sql/Memory.sql';
31import { type HeapSample } from '../../../js-heap/model/DatabaseStruct';
32import { SpStatisticsHttpUtil } from '../../../statistics/util/SpStatisticsHttpUtil';
33
34const TYPE_SNAPSHOT = 0;
35const TYPE_TIMELINE = 1;
36const LAMBDA_FUNCTION_NAME = '(anonymous)';
37export class SpArkTsChart implements ParseListener {
38  private trace: SpSystemTrace; // @ts-ignore
39  private folderRow: TraceRow<unknown> | undefined;
40  private jsCpuProfilerRow: TraceRow<JsCpuProfilerStruct> | undefined;
41  private heapTimelineRow: TraceRow<HeapTimelineStruct> | undefined;
42  private heapSnapshotRow: TraceRow<HeapSnapshotStruct> | undefined;
43  private loadJsDatabase: LoadDatabase;
44  private allCombineDataMap = new Map<number, JsCpuProfilerChartFrame>();
45  private process: string = '';
46
47  constructor(trace: SpSystemTrace) {
48    this.trace = trace;
49    this.loadJsDatabase = LoadDatabase.getInstance();
50  }
51
52  public get chartFrameMap(): Map<number, JsCpuProfilerChartFrame> {
53    return this.allCombineDataMap;
54  }
55
56  private cpuProfilerSupplierFrame(): void {
57    // @ts-ignore
58    this.jsCpuProfilerRow!.supplierFrame = (): Promise<Array<unknown>> => {
59      return cpuProfilerDataSender(this.jsCpuProfilerRow!).then((res: unknown) => {
60        // @ts-ignore
61        let maxHeight = res.maxDepth * 20;
62        this.jsCpuProfilerRow!.style.height = `${maxHeight}px`; // @ts-ignore
63        if (res.dataList.length > 0) {
64          this.allCombineDataMap = new Map<number, JsCpuProfilerChartFrame>(); // @ts-ignore
65          for (let data of res.dataList) {
66            this.allCombineDataMap.set(data.id, data);
67            SpSystemTrace.jsProfilerMap.set(data.id, data);
68          } // @ts-ignore
69          res.dataList.forEach((data: unknown) => {
70            // @ts-ignore
71            data.children = []; // @ts-ignore
72            if (data.childrenIds.length > 0) {
73              // @ts-ignore
74              for (let id of data.childrenIds) {
75                let child = SpSystemTrace.jsProfilerMap.get(Number(id)); // @ts-ignore
76                data.children.push(child);
77              }
78            } // @ts-ignore
79            data.name = SpSystemTrace.DATA_DICT.get(data.nameId) || LAMBDA_FUNCTION_NAME; // @ts-ignore
80            data.url = SpSystemTrace.DATA_DICT.get(data.urlId) || 'unknown'; // @ts-ignore
81            if (data.url && data.url !== 'unknown') {
82              // @ts-ignore
83              let dirs = data.url.split('/'); // @ts-ignore
84              data.scriptName = dirs.pop() || '';
85            }
86          });
87        } // @ts-ignore
88        return res.dataList;
89      });
90    };
91  }
92
93  private folderThreadHandler(): void {
94    this.folderRow!.onThreadHandler = (useCache): void => {
95      this.folderRow!.canvasSave(this.trace.canvasPanelCtx!);
96      if (this.folderRow!.expansion) {
97        // @ts-ignore
98        this.trace.canvasPanelCtx?.clearRect(0, 0, this.folderRow!.frame.width, this.folderRow!.frame.height);
99      } else {
100        (renders.empty as EmptyRender).renderMainThread(
101          {
102            context: this.trace.canvasPanelCtx,
103            useCache: useCache,
104            type: '',
105          },
106          this.folderRow!
107        );
108      }
109      this.folderRow!.canvasRestore(this.trace.canvasPanelCtx!, this.trace);
110    };
111  }
112
113  public async initFolder(): Promise<void> {
114    let jsConfig = await queryJsCpuProfilerConfig();
115    let jsCpu = await queryJsCpuProfilerData();
116    let jsMemory = await queryJsMemoryData();
117    if (jsMemory.length > 0 || jsCpu.length > 0) {
118      this.folderRow = TraceRow.skeleton();
119      //@ts-ignore
120      this.process = jsConfig[0].pid;
121      this.folderRow.rowId = this.process;
122      this.folderRow.rowType = TraceRow.ROW_TYPE_ARK_TS;
123      this.folderRow.style.height = '40px';
124      this.folderRow.rowParentId = '';
125      this.folderRow.folder = true;
126      this.folderRow.name = `Ark Ts ${this.process}`;
127      this.folderRow.addTemplateTypes('ArkTs');
128      this.folderRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
129      this.folderRow.selectChangeHandler = this.trace.selectChangeHandler;
130      this.folderRow.supplierFrame = (): Promise<Array<unknown>> =>
131        new Promise<Array<unknown>>((resolve) => resolve([]));
132      this.folderThreadHandler();
133      this.trace.rowsEL?.appendChild(this.folderRow); //@ts-ignore
134      if (this.folderRow && jsConfig[0].type !== -1 && jsMemory.length > 0) {
135        this.folderRow.addTemplateTypes('Memory');
136        if (
137          //@ts-ignore
138          jsConfig[0].type === TYPE_SNAPSHOT
139        ) {
140          // snapshot
141          await this.initSnapshotChart();
142        } else if (
143          //@ts-ignore
144          jsConfig[0].type === TYPE_TIMELINE
145        ) {
146          // timeline
147          await this.initTimelineChart();
148        }
149      }
150      //@ts-ignore
151      if (this.folderRow && jsConfig[0].enableCpuProfiler === 1 && jsCpu.length > 0) {
152        await this.initJsCpuChart();
153      }
154      if ((this.heapSnapshotRow || this.heapTimelineRow) && jsMemory.length > 0) {
155        await this.loadJsDatabase.loadDatabase(this);
156      }
157      if (this.jsCpuProfilerRow && jsCpu.length > 0) {
158        this.cpuProfilerSupplierFrame();
159      }
160      // 统计arkTs插件
161      let requsetBody = {
162        eventData:{
163          plugin:['arkts-plugin']
164        }
165      };
166      SpStatisticsHttpUtil.recordPluginUsage(requsetBody);
167    }
168  }
169
170  private async initTimelineChart(): Promise<void> {
171    this.heapTimelineRow = TraceRow.skeleton<HeapTimelineStruct>();
172    this.heapTimelineRow.rowParentId = this.process;
173    this.heapTimelineRow.rowHidden = !this.folderRow!.expansion;
174    this.heapTimelineRow.style.height = '40px';
175    this.heapTimelineRow.name = 'Heaptimeline';
176    this.heapTimelineRow.folder = false;
177    this.heapTimelineRow.rowType = TraceRow.ROW_TYPE_HEAP_TIMELINE;
178    this.heapTimelineRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
179    this.heapTimelineRow.selectChangeHandler = this.trace.selectChangeHandler;
180    this.heapTimelineRow.setAttribute('children', '');
181    this.heapTimelineRow!.focusHandler = (): void => {
182      this.trace?.displayTip(
183        this.heapTimelineRow!,
184        HeapTimelineStruct.hoverHeapTimelineStruct,
185        `<span>Size: ${Utils.getBinaryByteWithUnit(HeapTimelineStruct.hoverHeapTimelineStruct?.size || 0)}</span>`
186      );
187    };
188    this.heapTimelineRow!.findHoverStruct = (): void => {
189      HeapTimelineStruct.hoverHeapTimelineStruct = this.heapTimelineRow!.getHoverStruct();
190    };
191    this.folderRow!.addChildTraceRow(this.heapTimelineRow!);
192  }
193
194  private async initSnapshotChart(): Promise<void> {
195    this.heapSnapshotRow = TraceRow.skeleton<HeapSnapshotStruct>();
196    this.heapSnapshotRow.rowParentId = this.process;
197    this.heapSnapshotRow.rowHidden = !this.folderRow!.expansion;
198    this.heapSnapshotRow.style.height = '40px';
199    this.heapSnapshotRow.name = 'Heapsnapshot';
200    this.heapSnapshotRow.rowId = 'heapsnapshot';
201    this.heapSnapshotRow.folder = false;
202
203    this.heapSnapshotRow.rowType = TraceRow.ROW_TYPE_HEAP_SNAPSHOT;
204    this.heapSnapshotRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
205    this.heapSnapshotRow.selectChangeHandler = this.trace.selectChangeHandler;
206    this.heapSnapshotRow.setAttribute('children', '');
207    this.heapSnapshotRow!.focusHandler = (): void => {
208      this.trace?.displayTip(
209        this.heapSnapshotRow!,
210        HeapSnapshotStruct.hoverSnapshotStruct,
211        `<span>Name: ${HeapSnapshotStruct.hoverSnapshotStruct?.name || ''}</span>
212            <span>Size: ${Utils.getBinaryByteWithUnit(HeapSnapshotStruct.hoverSnapshotStruct?.size || 0)}</span>`
213      );
214    };
215    this.heapSnapshotRow!.findHoverStruct = (): void => {
216      HeapSnapshotStruct.hoverSnapshotStruct = this.heapSnapshotRow!.getHoverStruct();
217    };
218    this.folderRow!.addChildTraceRow(this.heapSnapshotRow);
219  }
220
221  private heapLineThreadHandler(samples: HeapSample[]): void {
222    this.heapTimelineRow!.onThreadHandler = (useCache): void => {
223      let context: CanvasRenderingContext2D;
224      if (this.heapTimelineRow?.currentContext) {
225        context = this.heapTimelineRow!.currentContext;
226      } else {
227        context = this.heapTimelineRow!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
228      }
229      this.heapTimelineRow!.canvasSave(context);
230      (renders['heap-timeline'] as HeapTimelineRender).renderMainThread(
231        {
232          context: context,
233          useCache: useCache,
234          type: 'heap-timeline',
235          samples: samples,
236        },
237        this.heapTimelineRow!
238      );
239      this.heapTimelineRow!.canvasRestore(context, this.trace);
240    };
241  }
242
243  private heapSnapshotThreadHandler(): void {
244    this.heapSnapshotRow!.onThreadHandler = (useCache): void => {
245      let context: CanvasRenderingContext2D;
246      if (this.heapSnapshotRow?.currentContext) {
247        context = this.heapSnapshotRow!.currentContext;
248      } else {
249        context = this.heapSnapshotRow!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
250      }
251      this.heapSnapshotRow!.canvasSave(context);
252      (renders['heap-snapshot'] as HeapSnapshotRender).renderMainThread(
253        {
254          context: context,
255          useCache: useCache,
256          type: 'heap-snapshot',
257        },
258        this.heapSnapshotRow!
259      );
260      this.heapSnapshotRow!.canvasRestore(context, this.trace);
261    };
262  }
263
264  public async parseDone(fileModule: Array<FileInfo>): Promise<void> {
265    if (fileModule.length > 0) {
266      let heapFile = HeapDataInterface.getInstance().getFileStructs();
267      let file = heapFile[0];
268      this.trace.snapshotFile = file;
269      if (file.type === TYPE_TIMELINE) {
270        let samples = HeapDataInterface.getInstance().getSamples(file.id);
271        this.heapTimelineRow!.rowId = `heaptimeline${file.id}`; // @ts-ignore
272        this.heapTimelineRow!.supplierFrame = (): Promise<unknown> =>
273          new Promise<unknown>((resolve) => resolve(samples));
274        this.heapLineThreadHandler(samples);
275      } else if (file.type === TYPE_SNAPSHOT) {
276        // @ts-ignore
277        this.heapSnapshotRow!.supplierFrame = (): Promise<Array<unknown>> =>
278          new Promise<Array<unknown>>((resolve) => resolve(heapFile));
279        this.heapSnapshotThreadHandler();
280      }
281    }
282  }
283
284  private initJsCpuChart = async (): Promise<void> => {
285    this.jsCpuProfilerRow = TraceRow.skeleton<JsCpuProfilerStruct>();
286    this.jsCpuProfilerRow.rowParentId = this.process;
287    this.jsCpuProfilerRow.rowHidden = !this.folderRow!.expansion;
288    this.jsCpuProfilerRow.name = 'CpuProfiler';
289    this.jsCpuProfilerRow.rowId = 'JsCpuProfiler';
290    this.jsCpuProfilerRow.folder = false;
291    this.jsCpuProfilerRow.rowType = TraceRow.ROW_TYPE_JS_CPU_PROFILER;
292    this.jsCpuProfilerRow!.style.height = '40px';
293    this.jsCpuProfilerRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
294    this.jsCpuProfilerRow.selectChangeHandler = this.trace.selectChangeHandler;
295    this.jsCpuProfilerRow.setAttribute('children', '');
296    this.jsCpuProfilerRow.focusHandler = (): void => {
297      this.trace?.displayTip(
298        this.jsCpuProfilerRow!,
299        JsCpuProfilerStruct.hoverJsCpuProfilerStruct,
300        `<span style='font-weight: bold;'>Name: </span>
301        <span>${JsCpuProfilerStruct.hoverJsCpuProfilerStruct?.name || ''}</span><br>
302        <span style='font-weight: bold;'>Self Time: </span>
303        <span>${ns2s(JsCpuProfilerStruct.hoverJsCpuProfilerStruct?.selfTime || 0)}</span><br>
304        <span style='font-weight: bold;'>Total Time: </span>
305        <span>${ns2s(JsCpuProfilerStruct.hoverJsCpuProfilerStruct?.totalTime || 0)}</span><br>
306        <span style='font-weight: bold;'>Url: </span>
307        <span>${JsCpuProfilerStruct.hoverJsCpuProfilerStruct?.url || 0}</span>`
308      );
309    };
310    this.jsCpuProfilerRow!.findHoverStruct = (): void => {
311      JsCpuProfilerStruct.hoverJsCpuProfilerStruct = this.jsCpuProfilerRow!.getHoverStruct();
312    };
313    this.jsCpuProfilerRow.onThreadHandler = (useCache): void => {
314      let context: CanvasRenderingContext2D;
315      if (this.jsCpuProfilerRow?.currentContext) {
316        context = this.jsCpuProfilerRow!.currentContext;
317      } else {
318        context = this.jsCpuProfilerRow!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
319      }
320      this.jsCpuProfilerRow!.canvasSave(context);
321      (renders['js-cpu-profiler'] as JsCpuProfilerRender).renderMainThread(
322        {
323          context: context,
324          useCache: useCache,
325          type: 'js-cpu-profiler',
326        },
327        this.jsCpuProfilerRow!
328      );
329      this.jsCpuProfilerRow!.canvasRestore(context, this.trace);
330    };
331    this.folderRow!.addChildTraceRow(this.jsCpuProfilerRow);
332  };
333}
334