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