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 */
15
16import { SpSystemTrace } from '../SpSystemTrace';
17import { TraceRow } from '../trace/base/TraceRow';
18import { procedurePool } from '../../database/Procedure';
19import { EBPFChartStruct, EBPFRender } from '../../database/ui-worker/ProcedureWorkerEBPF';
20import { ColorUtils } from '../trace/base/ColorUtils';
21import { Utils } from '../trace/base/Utils';
22import { renders } from '../../database/ui-worker/ProcedureWorker';
23import { EmptyRender } from '../../database/ui-worker/cpu/ProcedureWorkerCPU';
24import { diskIoSender, fileSysVMSender, fileSystemSender } from '../../database/data-trafic/EBPFSender';
25import { hasFileSysData } from '../../database/sql/Memory.sql';
26import { getDiskIOProcess } from '../../database/sql/SqlLite.sql';
27import { SpStatisticsHttpUtil } from '../../../statistics/util/SpStatisticsHttpUtil';
28
29export class SpEBPFChart {
30  private trace: SpSystemTrace;
31
32  constructor(trace: SpSystemTrace) {
33    this.trace = trace;
34  }
35
36  async init(): Promise<void> {
37    let sys = await hasFileSysData();
38    if (sys.length > 0) {
39      //@ts-ignore
40      let fsCount = sys[0].fsCount ?? 0;
41      //@ts-ignore
42      let vmCount = sys[0].vmCount ?? 0;
43      //@ts-ignore
44      let ioCount = sys[0].ioCount ?? 0;
45      if (sys && sys.length > 0 && (fsCount > 0 || vmCount > 0 || ioCount > 0)) {
46        let folder = await this.initFolder();
47        await this.initFileCallchain();
48        if (fsCount > 0) {
49          await this.initLogicalRead(folder);
50          await this.initLogicalWrite(folder);
51        }
52        if (vmCount > 0) {
53          await this.initVirtualMemoryTrace(folder);
54        }
55        if (ioCount > 0) {
56          await this.initDiskIOLatency(folder);
57          await this.initProcessDiskIOLatency(folder);
58        }
59        // 统计ebpf插件
60        let requsetBody = {
61          eventData: {
62            plugin: ['hiebpf-plugin']
63          }
64        };
65        SpStatisticsHttpUtil.recordPluginUsage(requsetBody);
66      }
67    }
68  }
69
70  async initFileCallchain(): Promise<unknown> {
71    return new Promise<unknown>((resolve, reject) => {
72      procedurePool.submitWithName('logic0', 'fileSystem-init', null, undefined, (res: unknown) => {
73        resolve(res);
74      });
75    });
76  }
77
78  //@ts-ignore
79  async initFolder(): Promise<TraceRow<unknown>> {
80    let fsFolder = TraceRow.skeleton();
81    fsFolder.rowId = 'FileSystem';
82    fsFolder.index = 0;
83    fsFolder.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM_GROUP;
84    fsFolder.rowParentId = '';
85    fsFolder.style.height = '40px';
86    fsFolder.folder = true;
87    fsFolder.name = 'EBPF'; /* & I/O Latency */
88    fsFolder.addTemplateTypes('HiEBpf');
89    fsFolder.favoriteChangeHandler = this.trace.favoriteChangeHandler;
90    fsFolder.selectChangeHandler = this.trace.selectChangeHandler; //@ts-ignore
91    fsFolder.supplierFrame = (): Promise<Array<unknown>> => new Promise<Array<unknown>>((resolve) => resolve([]));
92    fsFolder.onThreadHandler = (useCache): void => {
93      fsFolder.canvasSave(this.trace.canvasPanelCtx!);
94      if (fsFolder.expansion) {
95        // @ts-ignore
96        this.trace.canvasPanelCtx?.clearRect(0, 0, fsFolder.frame.width, fsFolder.frame.height);
97      } else {
98        (renders.empty as EmptyRender).renderMainThread(
99          {
100            context: this.trace.canvasPanelCtx,
101            useCache: useCache,
102            type: '',
103          },
104          fsFolder
105        );
106      }
107      fsFolder.canvasRestore(this.trace.canvasPanelCtx!, this.trace);
108    };
109    this.trace.rowsEL?.appendChild(fsFolder);
110    return fsFolder;
111  }
112
113  //@ts-ignore
114  async initLogicalRead(folder: TraceRow<unknown>): Promise<void> {
115    let logicalReadRow = TraceRow.skeleton<EBPFChartStruct>();
116    logicalReadRow.rowId = 'FileSystemLogicalRead';
117    logicalReadRow.index = 1;
118    logicalReadRow.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM;
119    logicalReadRow.rowParentId = folder.rowId;
120    logicalReadRow.rowHidden = !folder.expansion;
121    logicalReadRow.style.height = '40px';
122    logicalReadRow.setAttribute('children', '');
123    logicalReadRow.name = 'FileSystem Logical Read';
124    logicalReadRow.supplierFrame = async (): Promise<EBPFChartStruct[]> => {
125      const res = await fileSystemSender(2, TraceRow.range?.scale || 50, logicalReadRow);
126      return res;
127    };
128    logicalReadRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
129    logicalReadRow.selectChangeHandler = this.trace.selectChangeHandler;
130    logicalReadRow.focusHandler = (): void => this.focusHandler(logicalReadRow);
131    logicalReadRow.findHoverStruct = (): void => {
132      EBPFChartStruct.hoverEBPFStruct = logicalReadRow.getHoverStruct(false);
133    };
134    logicalReadRow.onThreadHandler = (useCache): void => {
135      let context: CanvasRenderingContext2D;
136      if (logicalReadRow.currentContext) {
137        context = logicalReadRow.currentContext;
138      } else {
139        context = logicalReadRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
140      }
141      logicalReadRow.canvasSave(context);
142      //@ts-ignore
143      (renders[TraceRow.ROW_TYPE_FILE_SYSTEM] as EBPFRender).renderMainThread(
144        {
145          context: context,
146          useCache: useCache,
147          type: `${TraceRow.ROW_TYPE_FILE_SYSTEM}-logical-read`,
148          chartColor: ColorUtils.MD_PALETTE[0],
149        },
150        logicalReadRow
151      );
152      logicalReadRow.canvasRestore(context, this.trace);
153    };
154    folder.addChildTraceRow(logicalReadRow);
155  }
156
157  //@ts-ignore
158  async initLogicalWrite(folder: TraceRow<unknown>): Promise<void> {
159    let logicalWriteRow = TraceRow.skeleton<EBPFChartStruct>();
160    logicalWriteRow.rowId = 'FileSystemLogicalWrite';
161    logicalWriteRow.index = 2;
162    logicalWriteRow.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM;
163    logicalWriteRow.rowParentId = folder.rowId;
164    logicalWriteRow.rowHidden = !folder.expansion;
165    logicalWriteRow.style.height = '40px';
166    logicalWriteRow.setAttribute('children', '');
167    logicalWriteRow.name = 'FileSystem Logical Write';
168    logicalWriteRow.supplierFrame = async (): Promise<EBPFChartStruct[]> => {
169      const res = await fileSystemSender(3, TraceRow.range?.scale || 50, logicalWriteRow);
170      return res;
171    };
172    logicalWriteRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
173    logicalWriteRow.selectChangeHandler = this.trace.selectChangeHandler;
174    logicalWriteRow.focusHandler = (): void => this.focusHandler(logicalWriteRow);
175    logicalWriteRow.findHoverStruct = (): void => {
176      EBPFChartStruct.hoverEBPFStruct = logicalWriteRow.getHoverStruct(false);
177    };
178    logicalWriteRow.onThreadHandler = (useCache): void => {
179      let context: CanvasRenderingContext2D;
180      if (logicalWriteRow.currentContext) {
181        context = logicalWriteRow.currentContext;
182      } else {
183        context = logicalWriteRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
184      }
185      logicalWriteRow.canvasSave(context);
186      //@ts-ignore
187      (renders[TraceRow.ROW_TYPE_FILE_SYSTEM] as EBPFRender).renderMainThread(
188        {
189          context: context,
190          useCache: useCache,
191          type: `${TraceRow.ROW_TYPE_FILE_SYSTEM}-logical-write`,
192          chartColor: ColorUtils.MD_PALETTE[8],
193        },
194        logicalWriteRow
195      );
196      logicalWriteRow.canvasRestore(context, this.trace);
197    };
198    folder.addChildTraceRow(logicalWriteRow);
199  }
200
201  //@ts-ignore
202  async initDiskIOLatency(folder: TraceRow<unknown>): Promise<void> {
203    let diskIoRow = TraceRow.skeleton<EBPFChartStruct>();
204    diskIoRow.rowId = 'FileSystemDiskIOLatency';
205    diskIoRow.index = 4;
206    diskIoRow.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM;
207    diskIoRow.rowParentId = folder.rowId;
208    diskIoRow.rowHidden = !folder.expansion;
209    diskIoRow.style.height = '40px';
210    diskIoRow.style.width = '100%';
211    diskIoRow.setAttribute('children', '');
212    diskIoRow.name = 'Disk I/O Latency';
213    diskIoRow.supplierFrame = async (): Promise<EBPFChartStruct[]> => {
214      const res = await diskIoSender(true, 0, [1, 2, 3, 4], TraceRow.range?.scale || 50, diskIoRow);
215      return res;
216    };
217    diskIoRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
218    diskIoRow.selectChangeHandler = this.trace.selectChangeHandler;
219    diskIoRow.focusHandler = (): void => this.focusHandler(diskIoRow);
220    diskIoRow.findHoverStruct = (): void => {
221      EBPFChartStruct.hoverEBPFStruct = diskIoRow.getHoverStruct(false);
222    };
223    diskIoRow.onThreadHandler = (useCache): void => {
224      let context: CanvasRenderingContext2D;
225      if (diskIoRow.currentContext) {
226        context = diskIoRow.currentContext;
227      } else {
228        context = diskIoRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
229      }
230      diskIoRow.canvasSave(context);
231      //@ts-ignore
232      (renders[TraceRow.ROW_TYPE_FILE_SYSTEM] as EBPFRender).renderMainThread(
233        {
234          context: context,
235          useCache: useCache,
236          type: `${TraceRow.ROW_TYPE_FILE_SYSTEM}-disk-io`,
237          chartColor: ColorUtils.MD_PALETTE[0],
238        },
239        diskIoRow
240      );
241      diskIoRow.canvasRestore(context, this.trace);
242    };
243    folder.addChildTraceRow(diskIoRow);
244  }
245
246  //@ts-ignore
247  initProcessDiskIOLatencyRead(i: number, folder: TraceRow<unknown>, process: unknown): TraceRow<EBPFChartStruct> {
248    let rowRead = TraceRow.skeleton<EBPFChartStruct>();
249    rowRead.index = 5 + 2 * i; //@ts-ignore
250    rowRead.rowId = `FileSystemDiskIOLatency-read-${process.ipid}`;
251    rowRead.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM;
252    rowRead.rowParentId = folder.rowId;
253    rowRead.rowHidden = !folder.expansion;
254    rowRead.style.height = '40px';
255    rowRead.style.width = '100%';
256    rowRead.setAttribute('children', ''); //@ts-ignore
257    rowRead.name = `${process.name ?? 'Process'}(${process.ipid}) Max Read Latency`;
258    rowRead.supplierFrame = async (): Promise<EBPFChartStruct[]> => {
259      //@ts-ignore
260      const res = await diskIoSender(false, process.ipid, [1, 3], TraceRow.range?.scale || 50, rowRead);
261      return res;
262    };
263    rowRead.favoriteChangeHandler = this.trace.favoriteChangeHandler;
264    rowRead.selectChangeHandler = this.trace.selectChangeHandler;
265    rowRead.focusHandler = (): void => this.focusHandler(rowRead);
266    rowRead.findHoverStruct = (): void => {
267      EBPFChartStruct.hoverEBPFStruct = rowRead.getHoverStruct(false);
268    };
269    rowRead.onThreadHandler = (useCache): void => {
270      let context: CanvasRenderingContext2D;
271      if (rowRead.currentContext) {
272        context = rowRead.currentContext;
273      } else {
274        context = rowRead.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
275      }
276      rowRead.canvasSave(context);
277      //@ts-ignore
278      (renders[TraceRow.ROW_TYPE_FILE_SYSTEM] as EBPFRender).renderMainThread(
279        {
280          context: context,
281          useCache: useCache, //@ts-ignore
282          type: `${TraceRow.ROW_TYPE_FILE_SYSTEM}-disk-io-process-read-${process.pid}`,
283          chartColor: ColorUtils.MD_PALETTE[0],
284        },
285        rowRead
286      );
287      rowRead.canvasRestore(context, this.trace);
288    };
289    return rowRead;
290  }
291
292  //@ts-ignore
293  private initProcessDiskIOWrite(i: number, folder: TraceRow<unknown>, process: unknown): TraceRow<EBPFChartStruct> {
294    let rowWrite = TraceRow.skeleton<EBPFChartStruct>();
295    rowWrite.index = 5 + 2 * i + 1; //@ts-ignore
296    rowWrite.rowId = `FileSystemDiskIOLatency-write-${process.ipid}`;
297    rowWrite.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM;
298    rowWrite.rowParentId = folder.rowId;
299    rowWrite.rowHidden = !folder.expansion;
300    rowWrite.style.height = '40px';
301    rowWrite.style.width = '100%';
302    rowWrite.setAttribute('children', ''); //@ts-ignore
303    rowWrite.name = `${process.name ?? 'Process'}(${process.pid}) Max Write Latency`;
304    rowWrite.supplierFrame = async (): Promise<EBPFChartStruct[]> => {
305      //@ts-ignore
306      const res = await diskIoSender(false, process.ipid, [2, 4], TraceRow.range?.scale || 50, rowWrite);
307      return res;
308    };
309    rowWrite.favoriteChangeHandler = this.trace.favoriteChangeHandler;
310    rowWrite.selectChangeHandler = this.trace.selectChangeHandler;
311    rowWrite.focusHandler = (): void => this.focusHandler(rowWrite);
312    rowWrite.findHoverStruct = (): void => {
313      EBPFChartStruct.hoverEBPFStruct = rowWrite.getHoverStruct(false);
314    };
315    rowWrite.onThreadHandler = (useCache): void => {
316      let context: CanvasRenderingContext2D;
317      if (rowWrite.currentContext) {
318        context = rowWrite.currentContext;
319      } else {
320        context = rowWrite.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
321      }
322      rowWrite.canvasSave(context);
323      //@ts-ignore
324      (renders[TraceRow.ROW_TYPE_FILE_SYSTEM] as EBPFRender).renderMainThread(
325        {
326          context: context,
327          useCache: useCache, //@ts-ignore
328          type: `${TraceRow.ROW_TYPE_FILE_SYSTEM}-disk-io-process-write-${process.pid}`,
329          chartColor: ColorUtils.MD_PALETTE[8],
330        },
331        rowWrite
332      );
333      rowWrite.canvasRestore(context, this.trace);
334    };
335    return rowWrite;
336  }
337
338  //@ts-ignore
339  async initProcessDiskIOLatency(folder: TraceRow<unknown>): Promise<void> {
340    let processes = (await getDiskIOProcess()) || [];
341    for (let i = 0, len = processes.length; i < len; i++) {
342      let process = processes[i];
343      const rowRead = this.initProcessDiskIOLatencyRead(i, folder, process);
344      folder.addChildTraceRow(rowRead);
345      const rowWrite = this.initProcessDiskIOWrite(i, folder, process);
346      folder.addChildTraceRow(rowWrite);
347    }
348  }
349
350  //@ts-ignore
351  async initVirtualMemoryTrace(folder: TraceRow<unknown>): Promise<void> {
352    let vmTraceRow = TraceRow.skeleton<EBPFChartStruct>();
353    vmTraceRow.rowId = 'FileSystemVirtualMemory';
354    vmTraceRow.index = 3;
355    vmTraceRow.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM;
356    vmTraceRow.rowParentId = folder.rowId;
357    vmTraceRow.rowHidden = !folder.expansion;
358    vmTraceRow.rangeSelect = true;
359    vmTraceRow.style.height = '40px';
360    vmTraceRow.style.width = '100%';
361    vmTraceRow.setAttribute('children', '');
362    vmTraceRow.name = 'Page Fault Trace';
363    vmTraceRow.supplierFrame = async (): Promise<EBPFChartStruct[]> => {
364      const res = await fileSysVMSender(TraceRow.range?.scale || 50, vmTraceRow);
365      return res;
366    };
367    vmTraceRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
368    vmTraceRow.selectChangeHandler = this.trace.selectChangeHandler;
369    vmTraceRow.focusHandler = (): void => this.focusHandler(vmTraceRow);
370    vmTraceRow.findHoverStruct = (): void => {
371      EBPFChartStruct.hoverEBPFStruct = vmTraceRow.getHoverStruct(false, false, 'size');
372    };
373    vmTraceRow.onThreadHandler = (useCache): void => {
374      let context: CanvasRenderingContext2D;
375      if (vmTraceRow.currentContext) {
376        context = vmTraceRow.currentContext;
377      } else {
378        context = vmTraceRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
379      }
380      vmTraceRow.canvasSave(context);
381      //@ts-ignore
382      (renders[TraceRow.ROW_TYPE_FILE_SYSTEM] as EBPFRender).renderMainThread(
383        {
384          context: context,
385          useCache: useCache,
386          type: `${TraceRow.ROW_TYPE_FILE_SYSTEM}-virtual-memory`,
387          chartColor: ColorUtils.MD_PALETTE[0],
388        },
389        vmTraceRow
390      );
391      vmTraceRow.canvasRestore(context, this.trace);
392    };
393    folder.addChildTraceRow(vmTraceRow);
394  }
395
396  focusHandler(row: TraceRow<EBPFChartStruct>): void {
397    let num = 0;
398    let tip = '';
399    if (EBPFChartStruct.hoverEBPFStruct) {
400      num = EBPFChartStruct.hoverEBPFStruct.size ?? 0;
401      let group10Ms = EBPFChartStruct.hoverEBPFStruct.group10Ms ?? false;
402      if (row.rowId!.startsWith('FileSystemDiskIOLatency')) {
403        if (num > 0) {
404          let tipStr = Utils.getProbablyTime(num);
405          if (group10Ms) {
406            tip = `<span>${tipStr} (10.00ms)</span>`;
407          } else {
408            tip = `<span>${tipStr}</span>`;
409          }
410        }
411      } else {
412        if (num > 0) {
413          if (group10Ms) {
414            tip = `<span>${num} (10.00ms)</span>`;
415          } else {
416            tip = `<span>${num}</span>`;
417          }
418        }
419      }
420    }
421    this.trace?.displayTip(row, EBPFChartStruct.hoverEBPFStruct, tip);
422  }
423}
424