• 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 { BaseElement, element } from '../../../../../base-ui/BaseElement';
17import { SelectionParam } from '../../../../bean/BoxSelection';
18import { LitTable, RedrawTreeForm } from '../../../../../base-ui/table/lit-table';
19import '../../../../../base-ui/slicer/lit-slicer';
20import '../../../../../base-ui/progress-bar/LitProgressBar';
21import { LitProgressBar } from '../../../../../base-ui/progress-bar/LitProgressBar';
22import { procedurePool } from '../../../../database/Procedure';
23import { Utils } from '../../base/Utils';
24import { FrameChart } from '../../../chart/FrameChart';
25import { FilterData, TabPaneFilter } from '../TabPaneFilter';
26import { ChartMode } from '../../../../bean/FrameChartStruct';
27import { SpSystemTrace } from '../../../SpSystemTrace';
28
29@element('tabpane-perf-async')
30export class TabPanePerfAsync extends BaseElement {
31  private mainTable: LitTable | undefined | null;
32  private showTable: LitTable | undefined | null;
33  private callStackTable: LitTable | undefined | null;
34  private progressEl: LitProgressBar | undefined;
35  private currentSelection: SelectionParam | undefined | null;
36  private currentData: Array<perfAsyncList> = [];
37  private simpleData: Array<perfAsyncList> = [];
38  private isChartShow: boolean = false;
39  private asyncFilter: TabPaneFilter | null | undefined;
40  private asyncFrameChart: FrameChart | null | undefined;
41  private searchValue: string = '';
42  private isSearch: boolean = false;
43  private processMap: Map<number, string> = new Map();
44  private threadMap: Map<number, string> = new Map();
45
46  /**
47   * 标签页点击后的入口
48   */
49  set data(perfAsyncSelection: SelectionParam | null | undefined) {
50    if (perfAsyncSelection === this.currentSelection) {
51      return;
52    }
53    this.searchValue = '';
54    // 保存当前的框选区域
55    this.currentSelection = perfAsyncSelection;
56    // 设置筛选框内容为空,切换图标显示成对应火焰图的
57    this.asyncFilter!.filterValue = '';
58    this.asyncFilter!.icon = 'block';
59    // 私有变量存储进程与线程的Map,后续根据id找对应的名字
60    this.processMap = Utils.getInstance().getProcessMap();
61    this.threadMap = Utils.getInstance().getThreadMap();
62    // 设置加载动画
63    this.progressEl!.loading = true;
64    // 提交子线程进行数据查询
65    this.submitQueryData(
66      {
67        tid: perfAsyncSelection?.perfThread!,
68        cpu: perfAsyncSelection?.perfCpus!,
69        pid: perfAsyncSelection?.perfProcess!,
70        leftNs: perfAsyncSelection?.leftNs!,
71        rightNs: perfAsyncSelection?.rightNs!,
72        eventId: perfAsyncSelection?.perfEventTypeId,
73        searchValue: this.searchValue
74      }
75    );
76  }
77
78  /**
79   * 提交线程查询数据库获取数据
80   * @param args 框选的perf泳道线程id等相关数据构成的对象
81   */
82  submitQueryData(args: {
83    tid?: Array<number>,
84    cpu?: Array<number>,
85    pid?: Array<number>,
86    leftNs: number,
87    rightNs: number,
88    eventId: number | undefined,
89    searchValue: string
90  }): void {
91    // 清空数据,保证筛选后没有数据时,相关的表格火焰图不会显示上次的结果
92    this.mainTable!.recycleDataSource = [];
93    this.showTable!.recycleDataSource = [];
94    this.callStackTable!.recycleDataSource = [];
95    this.asyncFrameChart!.data = [];
96    procedurePool.submitWithName('logic0', 'perf-async', args, undefined, this.callBack.bind(this));
97  }
98
99  /**
100   * worker线程返回查询结果后执行的回调函数
101   * @param result 数据库查询结果
102   */
103  callBack(result: Array<perfAsyncList>): void {
104    // 若返回数据为空,跳出函数
105    if (result.length === 0) {
106      this.progressEl!.loading = false;
107      // 切换显示火焰图还是表格
108      this.switchTableOrChart();
109      return;
110    }
111    // 用临时变量接收处理完的数据
112    this.currentData = this.organizeData(result);
113    // 简表使用数据
114    this.simpleData = this.generateSimpleData(this.simpleData);
115    // 设置表格数据
116    // 设置表格展开
117    if (this.isSearch) {
118      this.showTable!.isSearch = true;
119      this.showTable!.setStatus(this.simpleData, true);
120      this.isSearch = false;
121    }
122    this.tHeadClick(this.simpleData);
123    // 设置简表数据
124    this.showTable!.recycleDataSource = this.simpleData;
125    // 设置详表数据
126    this.mainTable!.recycleDataSource = this.currentData;
127    // 更新火焰图canvas
128    this.asyncFrameChart?.updateCanvas(true, this.clientWidth);
129    // 设置火焰图根据sampleCount还是eventCount分布绘制
130    if (this.currentSelection!.perfEventTypeId === undefined) {
131      this.asyncFrameChart!.mode = ChartMode.Count;
132    } else {
133      this.asyncFrameChart!.mode = ChartMode.EventCount;
134    }
135    // 设置火焰图数据
136    // @ts-ignore
137    this.asyncFrameChart!.data = this.currentData;
138    // 切换显示火焰图还是表格
139    this.switchTableOrChart();
140    this.progressEl!.loading = false;
141  };
142
143  /**
144   * 合并规则,进程->线程->callerStack_Id->栈顶->第一非相同栈->AsyncWorkCallback下一层结束
145   * @param result 数据库查询结果
146   * @returns 整理好的结果
147   */
148  organizeData(result: Array<perfAsyncList>): Array<perfAsyncList> {
149    // 最终显示到表格上的数组数据
150    let finalResult: Array<perfAsyncList> = [];
151    for (let i = 0; i < result.length; i++) {
152      // 处理每一条数据,配置基本项,并进行被调用栈切割,只保留asynccallback后的数据
153      this.dealEveryData(result[i]);
154      // 拷贝临时数组处理,否则会清掉引用类型变量原址的数据
155      let callStack: Array<perfAsyncList> = result[i].callerCallStack?.concat(result[i].calleeCallStack!)!;
156      // 进程数据
157      let processItem: perfAsyncList = { ...result[i] };
158      processItem.isProcess = true;
159      processItem.symbol = this.processMap.get(result[i].pid!) === null ?
160        'Process[' + result[i].pid! + ']' : this.processMap.get(result[i].pid!)! + '[' + result[i].pid! + ']';
161      // 线程数据
162      let threadItem: perfAsyncList = { ...result[i] };
163      threadItem.isThread = true;
164      threadItem.symbol = this.threadMap.get(result[i].tid!) === null ?
165        'Thread[' + result[i].tid! + ']' : this.threadMap.get(result[i].tid!)! + '[' + result[i].tid! + ']';
166      // @ts-ignore
167      callStack.unshift(threadItem);
168      // @ts-ignore
169      callStack.unshift(processItem);
170      // 递归将一维数组整理成链表
171      let callStackList: Array<perfAsyncList> = this.recursionToTree(callStack!, result[i])!;
172      // 如果两条数据calleechainid相同,则只保留前一条,并让sampleCount+1。如果不同,则找到被调用栈一样的的最后一层,将该条数据及其子数据插入进去
173      // 查找相同进程分组
174      const index: number = finalResult.findIndex((item) => item.pid === callStackList[0].pid);
175      if (index >= 0) {
176        finalResult[index].parent = undefined;
177        // 如果两条数据calleechainid相同,则只保留前一条,并让sampleCount+1。如果不同,则找到被调用栈一样的的最后一层,将该条数据及其子数据插入进去
178        recusion(callStackList[0], finalResult[index]);
179      } else {
180        finalResult.push(...callStackList);
181      }
182    }
183    // 保存数据,火焰图生成后处理简表数据
184    this.simpleData = [...result];
185    return finalResult;
186  }
187
188
189  /**
190   * 修改每一条数据的对应字段显示值
191   * @param data 数据库查询结果中的每一条数据
192   */
193  dealEveryData(data: perfAsyncList): void {
194    data.eventType = data.eventType + '[' + data.eventTypeId + ']';
195    data.sampleCount = 1;
196    data.symbol = Utils.getTimeString(data.time!);
197    const index: number | undefined = data.calleeCallStack?.findIndex((item) =>
198      item.symbolName!.indexOf('AsyncWorkCallback') !== -1
199    );
200    // 若不保留AsyncWorkCallback这一层,则在截取时让index+1
201    data.calleeCallStack = data.calleeCallStack?.slice(index!)!;
202    data.callStackList = data.callerCallStack?.concat(data.calleeCallStack!);
203    data.jsFuncName = data.callerCallStack![data.callerCallStack!.length - 1].symbolName;
204    data.asyncFuncName = data.calleeCallStack!.length > 1 ? data.calleeCallStack![1].symbolName : data.calleeCallStack![0].symbolName;
205    data.drawCount = 0;
206    data.drawDur = 0;
207    data.drawEventCount = 0;
208    data.drawSize = 0;
209    data.searchCount = 0;
210    data.searchDur = 0;
211    data.searchEventCount = 0;
212    data.searchSize = 0;
213    data.isChartSelect = false;
214    data.isChartSelectParent = false;
215    data.isDraw = false;
216    data.isJsStack = false;
217    data.isProcess = false;
218    data.isThread = false;
219    if (data.isSearch === undefined) {
220      data.isSearch = false;
221    }
222    data.count = 1;
223    data.size = 0;
224    data.dur = 1;
225    data.isCharged = false;
226  }
227
228  /**
229   * 用于设置右侧调用栈与被调用栈表格信息
230   * @param data 当前主表点击的行数据
231   */
232  setRightTableData(data: perfAsyncList): void {
233    const callStackList: Array<{
234      head: string,
235      eventTypeId: number,
236      symbolName: string,
237      children: Array<perfAsyncList>
238    }> = [];
239    if (!(data.isProcess || data.isThread)) {
240      if (data.callerCallchainid) {
241        callStackList.push({
242          head: '',
243          eventTypeId: 0,
244          symbolName: 'callerStack',
245          children: []
246        });
247        callStackList[0].children.push(...data.callerCallStack!);
248        if (data.calleeCallchainid) {
249          callStackList.push({
250            head: '',
251            eventTypeId: 0,
252            symbolName: 'calleeStack',
253            children: []
254          });
255          callStackList[1].children.push(...data.calleeCallStack!);
256        }
257      }
258    }
259    this.callStackTable!.recycleDataSource = callStackList;
260  }
261
262  /**
263   * 过滤工具栏的功能函数
264   * @param data 过滤工具栏相关数据
265   */
266  asyncListFilterGetFilter(data: FilterData): void {
267    if ((this.isChartShow && data.icon === 'tree') || (!this.isChartShow && data.icon === 'block')) {
268      this.switchTableOrChart();
269    } else if (this.searchValue !== this.asyncFilter!.filterValue) {
270      this.searchValue = this.asyncFilter!.filterValue;
271      this.submitQueryData(
272        {
273          tid: this.currentSelection?.perfThread!,
274          cpu: this.currentSelection?.perfCpus!,
275          pid: this.currentSelection?.perfProcess!,
276          leftNs: this.currentSelection?.leftNs!,
277          rightNs: this.currentSelection?.rightNs!,
278          eventId: this.currentSelection?.perfEventTypeId,
279          searchValue: this.searchValue
280        }
281      );
282      this.isSearch = true;
283    } else {
284      this.showTable!.setStatus(this.simpleData, true);
285      this.switchTableOrChart();
286    }
287  }
288
289  /**
290   * 点击按钮进行表格与火焰图的切换
291   * @param data 过滤工具栏相关数据
292   */
293  switchTableOrChart(): void {
294    let perfProfilerPageTab = this.shadowRoot?.querySelector('#show_table');
295    let perfProfilerPageChart = this.shadowRoot?.querySelector('#show_chart');
296    // 根据过滤工具栏的切换图标,显示火焰图或者表格
297    if (this.asyncFilter!.icon === 'block') {
298      perfProfilerPageChart?.setAttribute('class', 'show');
299      perfProfilerPageTab?.setAttribute('class', '');
300      this.isChartShow = true;
301      this.asyncFilter!.disabledMining = false;
302      this.asyncFrameChart?.calculateChartData();
303    } else if (this.asyncFilter!.icon === 'tree') {
304      perfProfilerPageChart?.setAttribute('class', '');
305      perfProfilerPageTab?.setAttribute('class', 'show');
306      this.isChartShow = false;
307      this.asyncFilter!.disabledMining = false;
308      this.asyncFrameChart!.clearCanvas();
309      this.showTable!.reMeauseHeight();
310    }
311  }
312
313  /**
314 * 递归,将一维数组的每一项作为上一项的子项整理成链表结构
315 * @param list 待整理的一维数组
316 * @param data 每一项增加属性值时需要读取内容的数据
317 * @param flag 待传入的每一个节点
318 * @returns 返回数组结构,一项套一项,例:原数据为长度12的数组,返回则为嵌套深度为12的单项数组
319 */
320  recursionToTree(
321    list: Array<perfAsyncList>,
322    data: perfAsyncList,
323    flag: Array<perfAsyncList> | null = null
324  ): Array<perfAsyncList> | undefined {
325    if (list.length === 0) {
326      return;
327    }
328    // 最后返回的数组,内容是一个链表
329    const tree: Array<perfAsyncList> = flag || [];
330    // 链表的每一层数据结构
331    let item: perfAsyncList = { ...data };
332    item.children = [];
333    // 此处定义tsArray是为了避免引用类型变量在进行数据合并,出现最底层栈却有多条数据的问题
334    item.tsArray = [item.time!];
335    item.parent = data;
336    // 循环一维数组,将每一项填充到其父级数组中
337    // @ts-ignore
338    item.symbol = list[0].symbolName || list[0].symbol;
339    // @ts-ignore
340    item.lib = list[0].lib || list[0].symbol;
341    // @ts-ignore
342    item.addr = list[0].addr || '';
343    // @ts-ignore
344    item.isProcess = list[0].isProcess;
345    // @ts-ignore
346    item.isThread = list[0].isThread;
347    // 符合搜索字符串匹配的火焰图及表格特殊处理
348    if (this.searchValue !== '' && item.symbol!.toLocaleLowerCase().indexOf(this.searchValue.toLocaleLowerCase()) !== -1) {
349      item.isSearch = true;
350    } else {
351      item.isSearch = false;
352    }
353    tree.push(item);
354    // 处理完的数据进行删除,每次循环只会拿到首项,也就是需要处理的子项
355    list.splice(0, 1);
356    // 递归调用
357    this.recursionToTree(list, item, tree[0].children!);
358    return tree;
359  }
360
361  /**
362   * 将数据库返回数据进行整理,生成简表展示数据
363   * @param data 数据库返回数据
364   * @returns 简表展示的数据
365   */
366  generateSimpleData(data: Array<perfAsyncList>): Array<perfAsyncList> {
367    let result = new Array<perfAsyncList>();
368    for (let i = 0; i < data.length; i++) {
369      let list = new Array<perfAsyncList>();
370      // 进程数据
371      let processItem: perfAsyncList = { ...data[i] };
372      processItem.isProcess = true;
373      processItem.symbol = this.processMap.get(data[i].pid!) === null ?
374        'Process[' + data[i].pid! + ']' : this.processMap.get(data[i].pid!)! + '[' + data[i].pid! + ']';
375      // 线程数据
376      let threadItem: perfAsyncList = { ...data[i] };
377      threadItem.isThread = true;
378      threadItem.symbol = this.threadMap.get(data[i].tid!) === null ?
379        'Thread[' + data[i].tid! + ']' : this.threadMap.get(data[i].tid!)! + '[' + data[i].tid! + ']';
380      // js栈层
381      let jsFuncItem: perfAsyncList = { ...data[i] };
382      jsFuncItem.symbol = jsFuncItem.jsFuncName;
383      // asyncWorkCallBack下一层
384      let asyncFuncItem: perfAsyncList = { ...data[i] };
385      asyncFuncItem.symbol = asyncFuncItem.asyncFuncName;
386      // 被调用栈最底层
387      let bottomItem: perfAsyncList = { ...data[i] };
388      bottomItem.asyncFuncName = bottomItem.calleeCallStack![bottomItem.calleeCallStack!.length - 1].symbolName;
389      bottomItem.symbol = bottomItem.asyncFuncName;
390      // 填充到数组,整理成链表结构
391      list.push(processItem, threadItem, jsFuncItem, asyncFuncItem, bottomItem);
392      // @ts-ignore
393      list = this.recursionToTree(list, data[i])!;
394      // 查找相同进程分组
395      const index: number = result.findIndex((item) => item.pid === list[0].pid);
396      if (index >= 0) {
397        result[index].parent = undefined;
398        // 合并栈
399        recusion(list[0], result[index], true);
400      } else {
401        result.push(...list);
402      }
403    }
404    return result;
405  }
406
407  /**
408   * 表头点击事件
409   */
410  private tHeadClick(data: Array<perfAsyncList>): void {
411    let labels = this.showTable?.shadowRoot?.querySelector('.th > .td')!.querySelectorAll('label');
412    if (labels) {
413      for (let i = 0; i < labels.length; i++) {
414        let label = labels[i].innerHTML;
415        labels[i].addEventListener('click', (e) => {
416          if (label.includes('Process') && i === 0) {
417            this.showTable!.setStatus(data, false);
418            this.showTable!.recycleDs = this.showTable!.meauseTreeRowElement(data, RedrawTreeForm.Retract);
419          } else if (label.includes('Thread') && i === 1) {
420            this.showTable!.setStatus(data, false, 0, 1);
421            this.showTable!.recycleDs = this.showTable!.meauseTreeRowElement(data, RedrawTreeForm.Retract);
422          } else if (label.includes('Js_Func') && i === 2) {
423            this.showTable!.setStatus(data, false, 0, 2);
424            this.showTable!.recycleDs = this.showTable!.meauseTreeRowElement(data, RedrawTreeForm.Retract);
425          } else if (label.includes('Async_Func') && i === 3) {
426            this.showTable!.setStatus(data, false, 0, 3);
427            this.showTable!.recycleDs = this.showTable!.meauseTreeRowElement(data, RedrawTreeForm.Retract);
428          } else if (label.includes('Bottom') && i === 4) {
429            this.showTable!.setStatus(data, true);
430            this.showTable!.recycleDs = this.showTable!.meauseTreeRowElement(data, RedrawTreeForm.Expand);
431          }
432        });
433      }
434    }
435  }
436
437  /**
438   * 初始化元素,并进行变量绑定,监听事件添加
439   */
440  initElements(): void {
441    this.mainTable = this.shadowRoot?.querySelector<LitTable>('#tb-perf-async');
442    this.showTable = this.shadowRoot?.querySelector<LitTable>('#tb-perf-show');
443    this.callStackTable = this.shadowRoot?.querySelector<LitTable>('#call-stack-list');
444    this.progressEl = this.shadowRoot?.querySelector('.perf-async-progress') as LitProgressBar;
445    this.asyncFilter = this.shadowRoot?.querySelector<TabPaneFilter>('#filter');
446    this.asyncFrameChart = this.shadowRoot?.querySelector<FrameChart>('#framechart');
447    let spApplication = document.querySelector('body > sp-application');
448    let spSystemTrace = spApplication?.shadowRoot?.querySelector(
449      'div > div.content > sp-system-trace'
450    ) as SpSystemTrace;
451    this.asyncFilter!.getFilterData(this.asyncListFilterGetFilter.bind(this));
452    this.asyncFilter?.addEventListener('focus', () => {
453      spSystemTrace.focusTarget = 'bottomUpInput';
454    });
455    this.asyncFilter?.addEventListener('blur', () => {
456      spSystemTrace.focusTarget = '';
457    });
458    // 将主表绑定行点击事件,根据点击数据更新右侧的调用栈与被调用栈表格信息
459    // @ts-ignore
460    this.showTable?.addEventListener('row-click', (evt: CustomEvent) => {
461      let data = evt.detail.data;
462      data.isSelected = true;
463      this.showTable?.clearAllSelection(data);
464      this.showTable?.setCurrentSelection(data);
465      this.setRightTableData(data);
466      document.dispatchEvent(
467        new CustomEvent('number_calibration', {
468          detail: { time: data.tsArray },
469        })
470      );
471      if (evt.detail.callBack) {
472        evt.detail.callBack(true);
473      }
474    });
475    // @ts-ignore
476    this.callStackTable?.addEventListener('row-click', (evt: CustomEvent) => {
477      let data = evt.detail.data;
478      data.isSelected = true;
479      this.callStackTable?.clearAllSelection(data);
480      this.callStackTable?.setCurrentSelection(data);
481      if (evt.detail.callBack) {
482        evt.detail.callBack(true);
483      }
484    });
485  }
486
487  /**
488   * 生命周期函数,用于调整表格高度
489   */
490  connectedCallback(): void {
491    super.connectedCallback();
492    // 设置火焰图滚动高度,更新火焰图时会
493    this.parentElement!.onscroll = (): void => {
494      this.asyncFrameChart!.tabPaneScrollTop = this.parentElement!.scrollTop;
495    };
496    let filterHeight = 0;
497    // 动态监听视图窗口变化
498    new ResizeObserver((entries): void => {
499      let asyncTabFilter = this.shadowRoot!.querySelector('#filter') as HTMLElement;
500      // 获取过滤工具栏高度
501      if (asyncTabFilter.clientHeight > 0) {
502        filterHeight = asyncTabFilter.clientHeight;
503      }
504      // 动态调整过滤工具栏的显隐
505      if (this.parentElement!.clientHeight > filterHeight) {
506        asyncTabFilter.style.display = 'flex';
507      } else {
508        asyncTabFilter.style.display = 'none';
509      }
510      // 如果数据表隐藏,则让过滤工具栏也隐藏
511      if (this.showTable!.style.visibility === 'hidden') {
512        asyncTabFilter.style.display = 'none';
513      }
514      if (this.parentElement?.clientHeight !== 0) {
515        // 更新火焰图
516        if (this.isChartShow) {
517          this.asyncFrameChart?.updateCanvas(false, entries[0].contentRect.width);
518          this.asyncFrameChart?.calculateChartData();
519        }
520        // 调整表格高度
521        // @ts-ignore
522        this.showTable?.shadowRoot.querySelector('.table').style.height =
523          // @ts-ignore
524          `${this.parentElement.clientHeight - 45}px`;
525        this.showTable?.reMeauseHeight();
526        // @ts-ignore
527        this.callStackTable?.shadowRoot.querySelector('.table').style.height =
528          `${this.parentElement!.clientHeight - 45}px`;
529        this.callStackTable?.reMeauseHeight();
530      }
531    }).observe(this.parentElement!);
532  }
533
534  /**
535   * 前端渲染的节点及样式
536   * @returns html元素字符串
537   */
538  initHtml(): string {
539    return this.initStyle() + `
540      <div class="perf-async-content" style="display: flex;flex-direction: column">
541        <lit-progress-bar class="progress perf-async-progress"></lit-progress-bar>
542        <selector id='show_table' class="show">
543          <lit-slicer style="width:100%">
544            <div id="left_table" style="width: 65%">
545              <lit-table id="tb-perf-async" style="height: 100%; overflow: auto; display: none" tree>
546                <lit-table-column width="550px" title="CallStack" data-index="symbol" key="symbol" align="flex-start" retract></lit-table-column>
547                <lit-table-column width="150px" title="Event_Count" data-index="eventCount" key="eventCount" align="flex-start"></lit-table-column>
548                <lit-table-column width="100px" title="Sample_Count" data-index="sampleCount" key="sampleCount" align="flex-start"></lit-table-column>
549                <lit-table-column width="150px" title="Js_Func_Name" data-index="jsFuncName" key="jsFuncName" align="flex-start"></lit-table-column>
550                <lit-table-column width="150px" title="Async_Func_Name" data-index="asyncFuncName" key="asyncFuncName" align="flex-start"></lit-table-column>
551                <lit-table-column width="150px" title="Trace_Id" data-index="traceid" key="traceid" align="flex-start"></lit-table-column>
552                <lit-table-column width="150px" title="CallerStack_Id" data-index="callerCallchainid" key="callerCallchainid" align="flex-start"></lit-table-column>
553                <lit-table-column width="150px" title="CalleeStack_Id" data-index="calleeCallchainid" key="calleeCallchainid" align="flex-start"></lit-table-column>
554              </lit-table>
555              <lit-table id="tb-perf-show" style="height: 100%; overflow: auto;" tree hideDownload>
556                <lit-table-column width="550px" title="Process/Thread/Js_Func/Async_Func/Bottom" data-index="symbol" key="symbol" align="flex-start" retract></lit-table-column>
557                <lit-table-column width="150px" title="Event_Count" data-index="eventCount" key="eventCount" align="flex-start"></lit-table-column>
558                <lit-table-column width="100px" title="Sample_Count" data-index="sampleCount" key="sampleCount" align="flex-start"></lit-table-column>
559                <lit-table-column width="150px" title="Js_Func_Name" data-index="jsFuncName" key="jsFuncName" align="flex-start"></lit-table-column>
560                <lit-table-column width="150px" title="Async_Func_Name" data-index="asyncFuncName" key="asyncFuncName" align="flex-start"></lit-table-column>
561                <lit-table-column width="150px" title="Trace_Id" data-index="traceid" key="traceid" align="flex-start"></lit-table-column>
562                <lit-table-column width="150px" title="CallerStack_Id" data-index="callerCallchainid" key="callerCallchainid" align="flex-start"></lit-table-column>
563                <lit-table-column width="150px" title="CalleeStack_Id" data-index="calleeCallchainid" key="calleeCallchainid" align="flex-start"></lit-table-column>
564              </lit-table>
565            </div>
566            <lit-slicer-track ></lit-slicer-track>
567            <div id='right_table' style='height: 100%; overflow: auto;width: 35%'>
568              <lit-table id="call-stack-list" hideDownload tree>
569                <lit-table-column width="35px" title="" data-index="head" key="head" align="flex-start" retract></lit-table-column>
570                <lit-table-column width="30px" title="" data-index="eventTypeId" key="eventTypeId"  align="flex-start">
571                  <template>
572                    <img src="img/library.png" size="20" v-if=" eventTypeId == 1 "> <img src="img/function.png" size="20" v-if=" eventTypeId == 0 ">
573                  </template>
574                </lit-table-column>
575                <lit-table-column width="1fr" title="Call_Stack" data-index="symbolName" key="symbolName" align="flex-start">
576                </lit-table-column>
577              </lit-table>
578            </div>
579          </lit-slicer>
580        </selector>
581        <tab-pane-filter id="filter" input inputLeftText icon perf></tab-pane-filter>
582        <selector id='show_chart'>
583            <tab-framechart id='framechart' style='width: 100%;height: auto'> </tab-framechart>
584        </selector>
585      </div>
586    `;
587  }
588
589  /**
590   * 前端渲染节点的样式
591   * @returns style标签
592   */
593  initStyle(): string {
594    return `
595      <style>
596        :host{
597          display: flex;
598          flex-direction: column;
599          padding: 10px 10px 0 10px;
600          height: calc(100% - 20px);
601        }
602        .perf-async-progress{
603          position: absolute;
604          height: 1px;
605          left: 0;
606          right: 0;
607        }
608        selector{
609          display: none;
610        }
611        .show{
612          display: flex;
613          flex: 1;
614        }
615        #perf-async-menu {
616          position: fixed;
617          bottom: 0;
618          width: 100%;
619          border: solid rgb(216,216,216) 1px;
620          float: left;
621          background: #f3f3f3;
622          height: 30px;
623        }
624        .refresh {
625          padding-left: 5px;
626          padding-top: 5px;
627        }
628        tab-pane-filter {
629            position: fixed;
630            bottom: 0;
631            width: 100%;
632            border: solid rgb(216,216,216) 1px;
633            float: left;
634        }
635        .perf-profile-progress{
636            bottom: 33px;
637            position: absolute;
638            height: 1px;
639            left: 0;
640            right: 0;
641        }
642      </style>
643     `;
644  }
645}
646
647interface perfAsyncList {
648  tid?: number;
649  pid?: number;
650  time?: number;
651  symbol?: string;
652  traceid?: string;
653  eventCount?: number;
654  sampleCount?: number;
655  jsFuncName?: string;
656  callerCallchainid?: number;
657  calleeCallchainid?: number;
658  asyncFuncName?: string;
659  eventType?: string;
660  children?: Array<perfAsyncList>;
661  eventTypeId?: number;
662  symbolName?: string;
663  callerCallStack?: Array<perfAsyncList>;
664  calleeCallStack?: Array<perfAsyncList>;
665  callStackList?: Array<perfAsyncList>;
666  parent?: perfAsyncList;
667  isProcess?: boolean;
668  isThread?: boolean;
669  depth?: number;
670  isSearch?: boolean;
671  isJsStack?: boolean;
672  lib?: string;
673  isChartSelectParent?: boolean;
674  isChartSelect?: boolean;
675  isDraw?: boolean;
676  drawDur?: number;
677  drawEventCount?: number;
678  drawCount?: number;
679  drawSize?: number;
680  searchEventCount?: number;
681  searchCount?: number;
682  searchDur?: number;
683  searchSize?: number;
684  size?: number;
685  count?: number;
686  dur?: number;
687  tsArray?: Array<number>;
688  isCharged?: boolean;
689  addr?: string;
690}
691
692/**
693   * 递归整理合并相同调用栈与被调用栈
694   * @param data 需要合并给目标数据的数据
695   * @param targetData 目标数据
696   * @param index 递归次数
697   */
698export function recusion(data: perfAsyncList, targetData: perfAsyncList, flag?: boolean): void {
699  // 将新元素合并到目标元素时,将目标元素的sampleCount和eventCount进行累加,每次进来都是要合并的值
700  targetData.sampleCount! += data.sampleCount!;
701  targetData.count! += data.count!;
702  targetData.dur! += data.dur!;
703  targetData.eventCount! += data.eventCount!;
704  targetData.tsArray?.push(data.time!);
705  targetData.tsArray = [...new Set(targetData.tsArray)];
706  data.parent = targetData.parent;
707  targetData.children?.sort((a, b) => b.count! - a.count!);
708  if (data.callerCallchainid !== targetData.callerCallchainid) {
709    targetData.jsFuncName = '';
710    // @ts-ignore
711    targetData.callerCallchainid = '';
712    targetData.traceid = '';
713  }
714  if (data.calleeCallchainid !== targetData.calleeCallchainid) {
715    targetData.asyncFuncName = '';
716    // @ts-ignore
717    targetData.calleeCallchainid = '';
718  }
719  // 需要根据子级是否有值判断如何合并,两者都有子级
720  if (data.children!.length !== 0 && targetData.children!.length !== 0) {
721    // 目标栈可能已经保存了多条数据,需要找到合并栈子级与被合并栈子级相同的分支进行栈合并
722    // 筛选出子项最多的那个
723    const num: number = targetData?.children?.findIndex((item) => item.symbol === data.children![0].symbol)!;
724    // 去重
725    targetData.tsArray = [...new Set(targetData.tsArray)];
726    // 若存在symbol相同的子级,则进入下一层合并。若不存在,则证明是新的分支,将新的子级填充到目标元素子级中
727    if (num >= 0) {
728      //此处是为了区分简表和详表。简表只有5层,存在首尾相同,中间不同的情况
729      if (flag && targetData.children![num].calleeCallchainid !== data.children![0].calleeCallchainid && data.children![0].children!.length === 0) {
730        targetData.children!.push(data.children![0]);
731        data.children![0].parent = targetData;
732      } else {
733        recusion(data.children![0], targetData.children![num], flag);
734      }
735    } else {
736      targetData.children?.push(data.children![0]);
737      data.children![0].parent = targetData;
738      targetData.children?.sort((a, b) => b.count! - a.count!);
739    }
740  } else if (data.children!.length !== 0 && targetData.children!.length === 0) {
741    // 若目标元素不存在子级,当前元素存在子级。证明目标元素中需要添加一个子级更多的元素,并将其填充到队首,方便查找相同分支时拿到的是子级更多的分支。
742    const num: number = targetData?.children?.findIndex((item) => item.symbol === data.children![0].symbol)!;
743    targetData.children?.splice(num, 0, data.children![0]);
744    data.children![0].parent = targetData;
745  }
746}