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 { renders } from '../../database/ui-worker/ProcedureWorker';
19import { SampleStruct, SampleRender } from '../../database/ui-worker/ProcedureWorkerBpftrace';
20import { queryStartTime } from '../../database/sql/SqlLite.sql';
21import { SpStatisticsHttpUtil } from '../../../statistics/util/SpStatisticsHttpUtil';
22
23export class SpBpftraceChart {
24  private trace: SpSystemTrace;
25
26  constructor(trace: SpSystemTrace) {
27    this.trace = trace;
28  }
29
30  async init(file: File | null) {
31    if (!file) {
32      let startTime = await queryStartTime();
33      //@ts-ignore
34      let folder = await this.initSample(startTime[0].start_ts, file);
35      this.trace.rowsEL?.appendChild(folder);
36    } else {
37      let folder = await this.initSample(-1, file);
38      this.trace.rowsEL?.appendChild(folder);
39    }
40  }
41
42  async initSample(start_ts: number, file: unknown): Promise<TraceRow<SampleStruct>> {
43    let traceRow = TraceRow.skeleton<SampleStruct>();
44    traceRow.rowId = 'bpftrace';
45    traceRow.index = 0;
46    traceRow.rowType = TraceRow.ROW_TYPE_SAMPLE;
47    traceRow.rowParentId = '';
48    traceRow.folder = false;
49    traceRow.style.height = '40px';
50    traceRow.name = 'bpftrace';
51    traceRow.selectChangeHandler = this.trace.selectChangeHandler;
52    traceRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
53    //添加上传按钮
54    if (!file) {
55      traceRow.addRowSampleUpload();
56    }
57    this.addTraceRowEventListener(traceRow, start_ts);
58    //单独上传
59    if (file) {
60      this.getJsonData(file).then((res: unknown) => {
61        // @ts-ignore
62        const propertyData = res.data;
63        // @ts-ignore
64        const treeNodes = res.relation.children || [res.relation.RS.children[0]];
65        const uniqueProperty = this.removeDuplicates(propertyData);
66        const flattenTreeArray = this.getFlattenTreeData(treeNodes);
67        // @ts-ignore
68        const height = (Math.max(...flattenTreeArray.map((obj: unknown) => obj.depth)) + 1) * 20;
69        // @ts-ignore
70        const sampleProperty = this.setRelationDataProperty(flattenTreeArray, uniqueProperty);
71        // @ts-ignore
72        const startTS = flattenTreeArray[0].property[0].begin;
73        traceRow.supplier = () =>
74          new Promise((resolve): void => {
75            // @ts-ignore
76            resolve(sampleProperty);
77          });
78        traceRow.onThreadHandler = (useCache) => {
79          let context: CanvasRenderingContext2D;
80          if (traceRow.currentContext) {
81            context = traceRow.currentContext;
82          } else {
83            context = traceRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
84          }
85          traceRow.canvasSave(context);
86          (renders.sample as SampleRender).renderMainThread(
87            {
88              context: context,
89              useCache: useCache,
90              type: 'bpftrace',
91              start_ts: startTS,
92              uniqueProperty: uniqueProperty,
93              // @ts-ignore
94              flattenTreeArray: flattenTreeArray,
95            },
96            traceRow
97          );
98          traceRow.canvasRestore(context);
99        };
100        traceRow.style.height = `${height}px`;
101      });
102    } else {
103      traceRow.supplier = () =>
104        new Promise((resolve): void => {
105          resolve([]);
106        });
107      traceRow.onThreadHandler = (useCache) => {
108        let context: CanvasRenderingContext2D;
109        if (traceRow.currentContext) {
110          context = traceRow.currentContext;
111        } else {
112          context = traceRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
113        }
114        traceRow.canvasSave(context);
115        (renders.sample as SampleRender).renderMainThread(
116          {
117            context: context,
118            useCache: useCache,
119            type: 'bpftrace',
120            start_ts: 0,
121            uniqueProperty: [],
122            flattenTreeArray: [],
123          },
124          traceRow
125        );
126        traceRow.canvasRestore(context);
127      };
128    }
129    return traceRow;
130  }
131
132  /**
133   * 监听文件上传事件
134   * @param row
135   * @param start_ts
136   */
137  // @ts-ignore
138  addTraceRowEventListener(row: TraceRow<unknown>, start_ts: number): void {
139    row.uploadEl?.addEventListener('sample-file-change', (e: unknown) => {
140      this.getJsonData(e).then((res: unknown) => {
141        this.resetChartData(row);
142        // @ts-ignore
143        const propertyData = res.data;
144        // @ts-ignore
145        const treeNodes = res.relation.children || [res.relation.RS.children[0]];
146        const uniqueProperty = this.removeDuplicates(propertyData);
147        const flattenTreeArray = this.getFlattenTreeData(treeNodes);
148        // @ts-ignore
149        const height = (Math.max(...flattenTreeArray.map((obj: unknown) => obj.depth)) + 1) * 20;
150        const sampleProperty = this.setRelationDataProperty(flattenTreeArray, uniqueProperty);
151        // @ts-ignore
152        const startTS = start_ts > 0 ? start_ts : flattenTreeArray[0].property[0].begin;
153        row.supplier = () =>
154          new Promise((resolve): void => {
155            resolve(sampleProperty);
156          });
157        row.onThreadHandler = (useCache) => {
158          let context: CanvasRenderingContext2D;
159          if (row.currentContext) {
160            context = row.currentContext;
161          } else {
162            context = row.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
163          }
164          row.canvasSave(context);
165          (renders.sample as SampleRender).renderMainThread(
166            {
167              context: context,
168              useCache: useCache,
169              type: 'bpftrace',
170              start_ts: startTS,
171              uniqueProperty: uniqueProperty,
172              // @ts-ignore
173              flattenTreeArray: flattenTreeArray,
174            },
175            row
176          );
177          row.canvasRestore(context);
178        };
179        row.style.height = `${height}px`;
180      });
181    });
182  }
183
184  /**
185   * 清空缓存
186   * @param row
187   */
188  // @ts-ignore
189  resetChartData(row: TraceRow<unknown>): void {
190    row.dataList = [];
191    row.dataList2 = [];
192    row.dataListCache = [];
193    row.isComplete = false;
194  }
195
196  /**
197   * 获取上传的文件内容 转为json格式
198   * @param file
199   * @returns
200   */
201  getJsonData(file: unknown): Promise<unknown> {
202    return new Promise((resolve, reject) => {
203      let reader = new FileReader();
204      // @ts-ignore
205      reader.readAsText(file.detail || file);
206      reader.onloadend = (e: unknown): void => {
207        // @ts-ignore
208        const fileContent = e.target?.result;
209        try {
210          resolve(JSON.parse(fileContent));
211          document.dispatchEvent(new CustomEvent('file-correct'));
212          SpStatisticsHttpUtil.addOrdinaryVisitAction({
213            event: 'bpftrace',
214            action: 'bpftrace',
215          });
216        } catch (error) {
217          document.dispatchEvent(new CustomEvent('file-error'));
218        }
219      };
220    });
221  }
222
223  /**
224   * 树结构扁平化
225   * @param treeData
226   * @param depth
227   * @param parentName
228   * @returns
229   */
230  getFlattenTreeData(treeData: Array<unknown>, depth: number = 0, parentName: string = ''): Array<unknown> {
231    let result: Array<object> = [];
232    treeData.forEach((node) => {
233      // @ts-ignore
234      const name: string = node.function_name;
235      const newNode: unknown = {};
236      if (name.indexOf('unknown') > -1) {
237        // @ts-ignore
238        newNode.children = this.getUnknownAllChildrenNames(node);
239      }
240      // @ts-ignore
241      newNode.detail = node.detail;
242      // @ts-ignore
243      newNode.depth = depth;
244      // @ts-ignore
245      newNode.name = name;
246      // @ts-ignore
247      newNode.parentName = parentName;
248      // @ts-ignore
249      newNode.property = [];
250      // @ts-ignore
251      result.push(newNode);
252      // @ts-ignore
253      if (node.children) {
254        // @ts-ignore
255        result = result.concat(this.getFlattenTreeData(node.children, depth + 1, node.function_name));
256      }
257    });
258    return result;
259  }
260
261  /**
262   * 查找重复项
263   * @param propertyData
264   * @returns
265   */
266  removeDuplicates(propertyData: Array<unknown>): Array<unknown> {
267    const result: Array<unknown> = [];
268    propertyData.forEach((propertyGroup) => {
269      const groups: Array<unknown> = [];
270      // @ts-ignore
271      propertyGroup.forEach((property: unknown) => {
272        // @ts-ignore
273        const duplicateObj = groups.find((group) => group.func_name === property.func_name);
274        if (duplicateObj) {
275          // @ts-ignore
276          duplicateObj.begin = Math.min(duplicateObj.begin, property.begin);
277          // @ts-ignore
278          duplicateObj.end = Math.max(duplicateObj.end, property.end);
279        } else {
280          groups.push(property);
281        }
282      });
283      result.push(groups);
284    });
285    return result;
286  }
287
288  /**
289   * 关系树赋值
290   * @param relationData
291   * @param propertyData
292   */
293  setRelationDataProperty(relationData: Array<unknown>, propertyData: Array<unknown>): Array<unknown> {
294    const sampleProperty = relationData;
295    //数组每一项进行比对
296    propertyData.forEach((propertyGroup) => {
297      // @ts-ignore
298      propertyGroup.forEach((property: unknown) => {
299        // @ts-ignore
300        const relation = sampleProperty.find((relation) => relation.name === property.func_name);
301        //property属性存储每帧数据
302        // @ts-ignore
303        relation?.property.push({
304          // @ts-ignore
305          name: property.func_name,
306          // @ts-ignore
307          detail: relation.detail,
308          // @ts-ignore
309          end: property.end,
310          // @ts-ignore
311          begin: property.begin,
312          // @ts-ignore
313          depth: relation.depth,
314          // @ts-ignore
315          instructions: property.instructions,
316          // @ts-ignore
317          cycles: property.cycles,
318        });
319      });
320    });
321
322    //获取所有名字为unknown的数据
323    // @ts-ignore
324    const unknownRelation = sampleProperty.filter((relation) => relation.name.indexOf('unknown') > -1);
325    //二维数组 用于存放unknown下所有子节点的数据
326    let twoDimensionalArray: Array<unknown> = [];
327    let result: Array<unknown> = [];
328    unknownRelation.forEach((unknownItem) => {
329      result = [];
330      twoDimensionalArray = [];
331      // @ts-ignore
332      const children = unknownItem.children;
333      //先获取到unknwon节点下每个子节点的property
334      Object.keys(children).forEach((key) => {
335        // @ts-ignore
336        unknownItem.children[key] = sampleProperty.find((relation) => relation.name === key).property;
337      });
338      //将每个子节点的property加到二维数组中
339      Object.values(children).forEach((value: unknown) => {
340        // @ts-ignore
341        if (value.length > 0) {
342          twoDimensionalArray.push(value);
343        }
344      });
345      if (twoDimensionalArray.length > 0) {
346        //取每列的最大值和最小值
347        // @ts-ignore
348        for (let i = 0; i < twoDimensionalArray[0].length; i++) {
349          const data = {
350            // @ts-ignore
351            name: unknownItem.name,
352            // @ts-ignore
353            detail: unknownItem.detail,
354            // @ts-ignore
355            begin: twoDimensionalArray[0][i].begin,
356            end: 0,
357            // @ts-ignore
358            depth: unknownItem.depth,
359          };
360          for (let j = 0; j < twoDimensionalArray.length; j++) {
361            // @ts-ignore
362            data.end = Math.max(twoDimensionalArray[j][i].end, data.end);
363            // @ts-ignore
364            data.begin = Math.min(twoDimensionalArray[j][i].begin, data.begin);
365          }
366          result.push(data);
367        }
368        // @ts-ignore
369        unknownItem.property = result;
370      }
371    });
372    return sampleProperty;
373  }
374
375  /**
376   * 获取unknown节点下所有孩子节点的名称
377   * @param node
378   * @param names
379   */
380  getUnknownAllChildrenNames(node: unknown, names: unknown = {}): object {
381    // @ts-ignore
382    if (node.children) {
383      // @ts-ignore
384      node.children.forEach((child: unknown) => {
385        // @ts-ignore
386        if (child.function_name.indexOf('unknown') < 0) {
387          // @ts-ignore
388          names[child.function.name] = [];
389        } else {
390          this.getUnknownAllChildrenNames(child, names);
391        }
392      });
393    }
394    // @ts-ignore
395    return names;
396  }
397}
398